L’obiettivo di questa analisi è quella di individuare il mondo che l’autore Haruki Murakami descrive e rappresenta attraverso i suoi misteriosi e cupi mondi, in bilico tra sogno e realtà, alla scoperta dell’esistenza umana. All’inizio dell’analisi mi sono concentrata nell’individuare gli aspetti tecnici e formali della scrittura murakamiana per poi passare all’analisi semantica dei libri basandomi principalemtne su queste domande: 1) individuare i libri che hanno fortemente influenzato Murakami 2) confronto tra primo e ultim romanzo 3) individuare temi ricorrenti nei libri 4) analizzare i libri in sequenza per individuare eventuali periodi dell’autore 5) individuare cosa vanno cercando i lettori di Murakami dai suoi romanzi, analizzando quelli più popolari
library(tidyverse)
## Warning: il pacchetto 'tidyverse' è stato creato con R versione 4.4.3
## Warning: il pacchetto 'ggplot2' è stato creato con R versione 4.4.3
## Warning: il pacchetto 'stringr' è stato creato con R versione 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
## Warning: il pacchetto 'tidytext' è stato creato con R versione 4.4.3
library(tidygraph)
##
## Caricamento pacchetto: 'tidygraph'
##
## Il seguente oggetto è mascherato da 'package:stats':
##
## filter
library(widyr)
## Warning: il pacchetto 'widyr' è stato creato con R versione 4.4.3
library(igraph)
## Warning: il pacchetto 'igraph' è stato creato con R versione 4.4.3
##
## Caricamento pacchetto: 'igraph'
##
## Il seguente oggetto è mascherato da 'package:tidygraph':
##
## groups
##
## I seguenti oggetti sono mascherati da 'package:lubridate':
##
## %--%, union
##
## I seguenti oggetti sono mascherati da 'package:dplyr':
##
## as_data_frame, groups, union
##
## I seguenti oggetti sono mascherati da 'package:purrr':
##
## compose, simplify
##
## Il seguente oggetto è mascherato da 'package:tidyr':
##
## crossing
##
## Il seguente oggetto è mascherato da 'package:tibble':
##
## as_data_frame
##
## I seguenti oggetti sono mascherati da 'package:stats':
##
## decompose, spectrum
##
## Il seguente oggetto è mascherato da 'package:base':
##
## union
library(ggraph)
library(scales)
## Warning: il pacchetto 'scales' è stato creato con R versione 4.4.3
##
## Caricamento pacchetto: 'scales'
##
## Il seguente oggetto è mascherato da 'package:purrr':
##
## discard
##
## Il seguente oggetto è mascherato da 'package:readr':
##
## col_factor
library(wordcloud)
## Warning: il pacchetto 'wordcloud' è stato creato con R versione 4.4.3
## Caricamento del pacchetto richiesto: RColorBrewer
library(RColorBrewer)
# Caricamento del dataset dei libri di murakami e dei libri ispirati da muraki
dfM <- read_rds("murakami_english.rds")
dfA <- read_rds("others_books.rds")
# Definisco un ordinamento per i libri
ordine_libri <- c(
"1-Hear the Wind Sing",
"2-Pinball, 1973",
"3-A Wild Sheep Chase",
"4-Hard Boiled Wonderland and the End of the World",
"5-Norwegian wood",
"6-Dance Dance Dance",
"7-South of the Border West of the Sun",
"8-The Wind-Up Bird Chronicle",
"9-Sputnik Sweetheart",
"10-Kafka On The Shore",
"11-After Dark",
"12-1Q84",
"13-Colorless Tsukuru Tazaki",
"14-Killing Commendatore",
"15-The City and Its Uncertain"
)
# Applica factor ai dataset principali
dfM <- dfM %>%
mutate(libro = factor(libro, levels = ordine_libri))
In questa sezione ho semplicemente caricato i dataframe dei 15 libri di Murakami (1-Hear the Wind Sing, 2-Pinball, 1973 , 3-A Wild Sheep Chase, 4-Hard Boiled Wonderland and the End of the World, 5-Norwegian wood, 6-Dance Dance Dance ,7-South of the Border West of the Sun, 8-The Wind-Up Bird Chronicle, 9-Sputnik Sweetheart, 10-Kafka On The Shore ,11-After Dark, 12-1Q84, 13-Colorless Tsukuru Tazaki, 14-Killing Commendatore, 15-The City and Its Uncertain). Inoltre ho caricato anche il dataframe con le opere principali di alcuni artisti che hanno avuto grande influenza sull’autore (1984,The Great Gatsby, The Long Goodbye, Trout Fishing in America).
#TOKENIZZAZIONE E PULIZIA
# visualizzo la struttura del dataset
glimpse(dfM)
## Rows: 615
## Columns: 5
## $ text <chr> "Hear the Wind Sing\n\n Haruki Murakami", "KODANSHA INTERNATIO…
## $ linea <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, …
## $ libro <fct> "1-Hear the Wind Sing", "1-Hear the Wind Sing", "1-Hear the Win…
## $ anno <dbl> 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 197…
## $ genere <chr> "Surrealism", "Surrealism", "Surrealism", "Surrealism", "Surrea…
# Tokenizzazione, un token (parola) per riga
libri_tidy <- dfM %>%
unnest_tokens(word, text)
# Rimozione delle stop words
libri_tidy <- libri_tidy %>%
anti_join(stop_words, by = "word")
glimpse(libri_tidy)
## Rows: 651,417
## Columns: 5
## $ linea <int> 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ libro <fct> "1-Hear the Wind Sing", "1-Hear the Wind Sing", "1-Hear the Win…
## $ anno <dbl> 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 1979, 197…
## $ genere <chr> "Surrealism", "Surrealism", "Surrealism", "Surrealism", "Surrea…
## $ word <chr> "hear", "wind", "sing", "haruki", "murakami", "kodansha", "inte…
# Salvataggio
#write_rds(libri_tidy, "murakami_english_tidy.rds")
In questa prima fase mi sono concentrata sulla preparazione del testo.
Osservando il dataframe iniziale tramite glimpse(dfM), emerge che il corpus è composto da 615 righe, ciascuna delle quali rappresenta un blocco di testo (capitoli) estratto per tutti i libri analizzati. Il dataset è organizzato in cinque colonne principali:
text, che contiene il testo vero e proprio;
linea, un identificativo progressivo del blocco;
libro, il titolo dell’opera considerata;
anno, che indica l’anno di pubblicazione;
genere, che classifica il genere letterario del libro;
A partire da questa struttura, il testo è stato sottoposto a tokenizzazione che trasforma il corpus dal formato “paragrafo” a un formato one-token-per-row che porta ad alcune trasformazioni automatiche come: - le parole vengono convertite in minuscolo - la punteggiatura viene rimossa - le informazioni contestuali (libro, anno, genere) vengono replicate per ciascun token. In questo modo, ogni parola resta sempre collegata al contesto narrativo da cui proviene.
Successivamente sono andata a rimuovere le stopword e quindi le parole molto frequenti nella lingua inglese che hanno un peso informativo ridotto e rischiano di coprire termini più significativi dal punto di vista semantico.
Ho scelto di non applicare lo stemming perché non volevo rischiare di ridurre parole diverse a una stessa radice e perdere poi le varie sfumature semantiche.
levels(libri_tidy$libro)
## [1] "1-Hear the Wind Sing"
## [2] "2-Pinball, 1973"
## [3] "3-A Wild Sheep Chase"
## [4] "4-Hard Boiled Wonderland and the End of the World"
## [5] "5-Norwegian wood"
## [6] "6-Dance Dance Dance"
## [7] "7-South of the Border West of the Sun"
## [8] "8-The Wind-Up Bird Chronicle"
## [9] "9-Sputnik Sweetheart"
## [10] "10-Kafka On The Shore"
## [11] "11-After Dark"
## [12] "12-1Q84"
## [13] "13-Colorless Tsukuru Tazaki"
## [14] "14-Killing Commendatore"
## [15] "15-The City and Its Uncertain"
# 1: calcolo la lunghezza media delle parole
# Lunghezza complessiva
libri_tidy %>%
mutate(word_length = nchar(word)) %>%
summarise(media_lunghezza = mean(word_length))
## # A tibble: 1 × 1
## media_lunghezza
## <dbl>
## 1 6.12
# Lunghezza per ciascun libro
lunghezza_per_libro <- libri_tidy %>%
mutate(word_length = nchar(word)) %>%
group_by(libro) %>%
summarise(media_lunghezza = mean(word_length)) %>%
arrange(libro)
print(lunghezza_per_libro, n = 15)
## # A tibble: 15 × 2
## libro media_lunghezza
## <fct> <dbl>
## 1 1-Hear the Wind Sing 5.86
## 2 2-Pinball, 1973 5.99
## 3 3-A Wild Sheep Chase 6.19
## 4 4-Hard Boiled Wonderland and the End of the World 6.17
## 5 5-Norwegian wood 5.89
## 6 6-Dance Dance Dance 6.08
## 7 7-South of the Border West of the Sun 6.10
## 8 8-The Wind-Up Bird Chronicle 6.12
## 9 9-Sputnik Sweetheart 6.01
## 10 10-Kafka On The Shore 5.95
## 11 11-After Dark 5.91
## 12 12-1Q84 6.24
## 13 13-Colorless Tsukuru Tazaki 6.19
## 14 14-Killing Commendatore 6.22
## 15 15-The City and Its Uncertain 6.06
# Distribuzione complessiva
libri_tidy %>%
mutate(word_length = nchar(word)) %>%
ggplot(aes(x = word_length)) +
geom_histogram(binwidth = 1, fill = "steelblue", color = "black") +
labs(title = "Word length distribution — Murakami corpus",
x = "Word length",
y = "Frequency") +
theme_minimal()
# Distribuzione per libro
libri_tidy %>%
mutate(word_length = nchar(word)) %>%
ggplot(aes(x = word_length, fill = libro)) +
geom_histogram(binwidth = 1, color = "black") +
facet_wrap(~libro, scales = "free_y") +
theme_minimal() +
theme(legend.position = "none") +
labs(title = "Word length distribution per book",
x = "Word length",
y = "Frequency")
#2: calcolo la frequenza delle parole
# Frequenze assolute globali
word_frequencies <- libri_tidy %>%
count(word, sort = TRUE)
# Visualizzazione top 20 parole più frequenti
word_frequencies %>%
top_n(20, n) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Most frequent words — Murakami corpus",
x = NULL,
y = "Word count") +
theme_minimal()
# Word cloud complessivo
set.seed(42)
word_frequencies %>%
with(wordcloud(word, n,
max.words = 100,
random.order = FALSE,
colors = brewer.pal(8, "Dark2")))
# Frequenze assolute per libro
word_freq_per_book <- libri_tidy %>%
count(libro, word, sort = TRUE)
# Frequenze relative per libro
word_freq_per_book_rel <- word_freq_per_book %>%
group_by(libro) %>%
mutate(proportion = n / sum(n)) %>%
ungroup()
# Visualizzazione top 10 parole per libro
word_freq_per_book_rel %>%
group_by(libro) %>%
slice_max(proportion, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, proportion, libro)) %>%
ggplot(aes(word, proportion, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Most frequent words per book (relative frequency)",
x = NULL,
y = "Relative frequency") +
theme_minimal()
# Palette ottimale per scegliere il colore più corretto
palette_wordcloud <- brewer.pal(8, "Set1")
unique_books <- levels(libri_tidy$libro) #individuo i libri
# Loop attraverso i libri nell'ordine corretto
for (book_title in unique_books) {
cat("\n=== Word Cloud for:", book_title, "===\n")
book_data <- word_freq_per_book %>%
filter(libro == book_title) %>%
slice_max(n, n = 100)
# Verifica che ci siano dati
if (nrow(book_data) > 0) {
set.seed(42)
wordcloud(words = book_data$word,
freq = book_data$n,
max.words = 100,
random.order = FALSE,
rot.per = 0.2,
colors = palette_wordcloud,
scale = c(4, 0.5))
}
}
##
## === Word Cloud for: 1-Hear the Wind Sing ===
##
## === Word Cloud for: 2-Pinball, 1973 ===
##
## === Word Cloud for: 3-A Wild Sheep Chase ===
##
## === Word Cloud for: 4-Hard Boiled Wonderland and the End of the World ===
##
## === Word Cloud for: 5-Norwegian wood ===
##
## === Word Cloud for: 6-Dance Dance Dance ===
##
## === Word Cloud for: 7-South of the Border West of the Sun ===
##
## === Word Cloud for: 8-The Wind-Up Bird Chronicle ===
##
## === Word Cloud for: 9-Sputnik Sweetheart ===
##
## === Word Cloud for: 10-Kafka On The Shore ===
##
## === Word Cloud for: 11-After Dark ===
##
## === Word Cloud for: 12-1Q84 ===
##
## === Word Cloud for: 13-Colorless Tsukuru Tazaki ===
##
## === Word Cloud for: 14-Killing Commendatore ===
##
## === Word Cloud for: 15-The City and Its Uncertain ===
#3: calcolo la legge di zipf
# Calcolo rank e term frequency per il dataset complessivo
zipf_data <- word_frequencies %>%
mutate(rank = row_number(),
`term frequency` = n / sum(n))
# Plot log-log
zipf_data %>%
ggplot(aes(rank, `term frequency`)) +
geom_line(linewidth = 1.1, alpha = 0.8, color = "red") +
scale_x_log10() +
scale_y_log10() +
labs(title = "Zipf's law — Murakami corpus",
x = "Rank",
y = "Term frequency") +
theme_minimal()
# Fitting della power law
rank_subset <- zipf_data %>%
filter(rank > 10, rank < 500)
mod <- lm(log10(`term frequency`) ~ log10(rank), data = rank_subset)
# Stampa coefficienti
cat("\n=== Zipf's law model ===\n")
##
## === Zipf's law model ===
cat("Intercept:", mod$coefficients[1], "\n")
## Intercept: -1.919312
cat("Slope:", mod$coefficients[2], "\n")
## Slope: -0.5444061
# Plot con linea di fitting
zipf_data %>%
ggplot(aes(rank, `term frequency`)) +
geom_abline(intercept = mod$coefficients[1],
slope = mod$coefficients[2],
color = "gray50",
linetype = 2,
linewidth = 1) +
geom_line(linewidth = 1.1, alpha = 0.8, color = "red") +
scale_x_log10() +
scale_y_log10() +
labs(title = "Zipf's law with power law fit — Murakami corpus",
subtitle = paste0("Slope = ", round(mod$coefficients[2], 3)),
x = "Rank",
y = "Term frequency") +
theme_minimal()
# calcolo la legge di zipf per ogni libro
zipf_per_book <- word_freq_per_book %>%
group_by(libro) %>%
mutate(rank = row_number(),
`term frequency` = n / sum(n))
zipf_per_book %>%
ggplot(aes(rank, `term frequency`, color = libro)) +
geom_line(linewidth = 1.1, alpha = 0.8, show.legend = FALSE) +
scale_x_log10() +
scale_y_log10() +
facet_wrap(~libro, scales = "free") +
labs(title = "Zipf's law per book",
x = "Rank",
y = "Term frequency") +
theme_minimal()
#4: individuo le parole distintive per ogni libro con tf-idf
book_words <- libri_tidy %>%
count(libro, word, sort = TRUE)
# Calcolo tf-idf
book_words <- book_words %>%
bind_tf_idf(word, libro, n)
# Verifica: parole con tf-idf più alto
cat("\n=== Top tf-idf words across all books ===\n")
##
## === Top tf-idf words across all books ===
book_words %>%
arrange(desc(tf_idf)) %>%
print(n = 20)
## # A tibble: 117,855 × 6
## libro word n tf idf tf_idf
## <fct> <chr> <int> <dbl> <dbl> <dbl>
## 1 13-Colorless Tsukuru Tazaki tsukuru 892 0.0315 2.01 0.0635
## 2 12-1Q84 tengo 2860 0.0207 2.71 0.0561
## 3 12-1Q84 aomame 2164 0.0157 2.71 0.0424
## 4 11-After Dark mari 335 0.0207 2.01 0.0417
## 5 14-Killing Commendatore menshiki 930 0.0120 2.71 0.0326
## 6 9-Sputnik Sweetheart sumire 433 0.0201 1.32 0.0266
## 7 10-Kafka On The Shore hoshino 528 0.00924 2.71 0.0250
## 8 11-After Dark kaoru 149 0.00921 2.71 0.0249
## 9 5-Norwegian wood midori 312 0.00866 2.71 0.0235
## 10 6-Dance Dance Dance yuki 340 0.00865 2.71 0.0234
## 11 9-Sputnik Sweetheart miu 379 0.0176 1.32 0.0233
## 12 5-Norwegian wood reiko 295 0.00819 2.71 0.0222
## 13 14-Killing Commendatore mariye 575 0.00744 2.71 0.0202
## 14 11-After Dark takahashi 154 0.00952 2.01 0.0192
## 15 13-Colorless Tsukuru Tazaki haida 187 0.00661 2.71 0.0179
## 16 10-Kafka On The Shore oshima 371 0.00649 2.71 0.0176
## 17 6-Dance Dance Dance gotanda 233 0.00593 2.71 0.0160
## 18 7-South of the Border West of the Sun shimamoto 172 0.00968 1.61 0.0156
## 19 13-Colorless Tsukuru Tazaki sara 218 0.00771 2.01 0.0155
## 20 12-1Q84 fuka 785 0.00568 2.71 0.0154
## # ℹ 117,835 more rows
p_tfidf_parte1 <- book_words %>%
filter(libro %in% levels(libro)[1:8]) %>%
group_by(libro) %>%
top_n(15, tf_idf) %>%
ungroup() %>%
mutate(word = reorder_within(word, tf_idf, libro)) %>%
ggplot(aes(word, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive words per book (tf-idf)",
x = NULL,
y = "tf-idf") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
p_tfidf_parte2 <- book_words %>%
filter(libro %in% levels(libro)[9:15]) %>%
group_by(libro) %>%
top_n(15, tf_idf) %>%
ungroup() %>%
mutate(word = reorder_within(word, tf_idf, libro)) %>%
ggplot(aes(word, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive words per book (tf-idf)",
x = NULL,
y = "tf-idf") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
print(p_tfidf_parte1)
print(p_tfidf_parte2)
L’analisi della lunghezza media delle parole evidenzia che il vocabolario di Murakami, anche dopo la rimozione delle stopword, risulta mediamente più complesso rispetto all’inglese standard (4.7-5.5 caratteri). Questo avviene perchè Murakami predilige una scrittura densa, precisa, in cui ogni parola tende a portare con sé un particolare peso semantico.
Osservando l’evoluzione nel tempo, emerge una progressiva maturazione stilistica. I romanzi dell’esordio, come Hear the Wind Sing (5.86 caratteri), mostrano un linguaggio più essenziale e diretto, coerente con l’estetica minimalista degli anni Settanta e con l’influenza del romanzo americano mentre le opere più tarde come 1Q84 (6.24 caratteri) o Killing Commendatore (6.22) mantengono invece una complessità lessicale più elevata e stabile.
Per quanto riguarda le frequenze delle parole, emerge in maniera evidente l’ossessione che Murakami ha per il tempo. La parola time con 6,648 occorrenze domina l’intero corpus e trova eco in termini come day e night che confermano l’importanza che la parola assume durante lo sviluppo della narrativa. Il tempo non viene trattato come un semplice riferimento cronologico, ma come una riflessione esistenziale sulla natura sfuggente del tempo, sulla memoria, sulla perdita e sul rimpianto. Dai mondi paralleli di 1Q84 alla nostalgia dolorosa di Norwegian Wood, il tempo è sempre qualcosa che sfugge, si deforma o ritorna sotto forma di ricordo.
Accanto al tempo, un altro nucleo centrale riguarda la percezione sensoriale e in particolare quella visiva. Parole come eyes (2,275) e looked (2,175) compaiono con frequenza e rimandano a protagonisti contemplativi, osservatori passivi della realtà più che attivi. Nei romanzi di Murakami, lo sguardo diventa spesso un canale di accesso all’interiorità del personaggio, guardare significa cercare un senso alle dinamiche narrate, ma anche un atto di distanza dal mondo.
La triade “people” (2,814), “world” (2,240), “life” (1,900+) evidenzia la vocazione filosofica di Murakami che si concentra su tre temi principali:
Il rapporto tra l’individuo e la collettività (“people”) La natura della realtà e delle realtà parallele (“world”) Il senso dell’esistenza (“life”)
Questa dimensione universale coesiste con quella intimista, creando il caratteristico equilibrio murakamiano tra quotidiano e metafisico.
Durante l’analisi è evidente il peso rilevante di 1Q84 all’interno del corpus. Essendo uno dei romanzi più lunghi e complessi dell’artista (oltre 1,300 pagine nelle edizioni inglesi), i nomi Tengo e Aomame emergono in modo dominante, riflettendo la struttura duale nel quale i due protagonisti coesistono, costruita su due linee narrative parallele che si rispecchiano e si rincorrono. La ripetizione insistita dei nomi propri, più che dei pronomi, è coerente con una tecnica narrativa che mantiene una certa distanza emotiva, pur insistendo sull’identità individuale dei personaggi.
I word cloud permettono di cogliere visivamente ciò che le frequenze suggeriscono, ogni romanzo possiede una propria identità tematica ben riconoscibile. I primi lavori ruotano attorno a oggetti quotidiani e simboli ricorrenti (bar, sigarette, flipper),
In After Dark domina la parola time affiancata dal nome Mari, un dato che riflette perfettamente la struttura del romanzo concepito come una lunga notte urbana scandita quasi in tempo reale.
Norwegian Wood presenta invece una configurazione fortemente relazionale. I nomi Naoko e Midori emergono con peso quasi identico, confermando la centralità del triangolo emotivo che struttura il romanzo. Il lessico è intimo e corporeo, legato alla vita quotidiana e alle relazioni. Nel word cloude si nota l’assenza relativa della parola love, mai esplicitata ma costantemente vissuta attraverso memoria, dolore e perdita.
In Dance Dance Dance, la parola dominante è hotel, a indicare come lo spazio (in particolare il Dolphin Hotel) assuma un ruolo centrale e simbolico. Il romanzo è attraversato da un lessico urbano e percettivo che richiama l’estetica hard-boiled e mette in scena personaggi diversi per età e visione del mondo, accomunati da una profonda alienazione. L’assenza della parola dance sottolinea come il titolo alluda non a un’azione concreta, ma a una condizione esistenziale, il continuare a muoversi senza comprenderne fino in fondo il senso.
Nel loro insieme, queste tre word cloud funzionano come veri e propri ritratti narrativi. After Dark è dominato dal tempo e dall’osservazione, Norwegian Wood dalla memoria e dalle relazioni, Dance Dance Dance dallo spazio urbano e dalla perdita di identità. Le visualizzazioni rendono così immediatamente visibile l’anima di ciascun romanzo.
Dal punto di vista linguistico, l’applicazione della legge di Zipf conferma una notevole coerenza stilistica, nonostante l’arco temporale di oltre quarant’anni e la varietà di temi affrontati, Murakami mantiene una distribuzione lessicale sorprendentemente uniforme. Questo suggerisce che ciò che cambia radicalmente da un romanzo all’altro non è tanto il linguaggio di base, quanto l’universo narrativo che quel linguaggio abita. Nel caso di Murakami, lo slope meno ripido (-0.544) indica che la distribuzione delle frequenze decade più lentamente del previsto e questo perchè lo stile di murakami è molto minimalista.
Infine, l’analisi TF-IDF rafforza questa intuizione, le parole che distinguono maggiormente i romanzi tra loro sono quasi sempre i nomi dei personaggi. Murakami utilizza lo stesso vocabolario fondamentale per raccontare storie molto diverse, sono i personaggi, con i loro nomi e le loro ossessioni, a definire l’identità di ciascun libro.
Murakami costruisce un universo linguistico riconoscibile, all’interno del quale ogni romanzo rappresenta una nuova declinazione di temi, simboli e personaggi. È proprio questo che rendere le sue opere così immediatamente identificabili e rappresentative.
# 1: BIGRAMMI
# Tokenizzazione in bigrammi
bigrams <- dfM %>%
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
filter(!is.na(bigram))
bigrams_separated <- bigrams %>%
separate(bigram, c("word1", "word2"), sep = " ")
bigrams_filtered <- bigrams_separated %>%
filter(!word1 %in% stop_words$word,
!word2 %in% stop_words$word)
bigram_counts <- bigrams_filtered %>%
count(word1, word2, sort = TRUE)
cat("\n=== Top 20 bigrams (corpus) ===\n")
##
## === Top 20 bigrams (corpus) ===
print(bigram_counts, n = 20)
## # A tibble: 120,768 × 3
## word1 word2 n
## <chr> <chr> <int>
## 1 fuka eri 712
## 2 air chrysalis 317
## 3 miss saeki 193
## 4 noboru wataya 182
## 5 creta kano 165
## 6 tomohiko amada 149
## 7 deep breath 141
## 8 malta kano 119
## 9 left hand 118
## 10 elementary school 114
## 11 middle aged 113
## 12 dolphin hotel 112
## 13 front door 110
## 14 ten minutes 109
## 15 time ago 105
## 16 real world 103
## 17 yellow submarine 102
## 18 killing commendatore 96
## 19 coffee shop 86
## 20 shoulder bag 86
## # ℹ 120,748 more rows
# Ricostruisci bigrammi come stringa
bigrams_united <- bigrams_filtered %>%
unite(bigram, word1, word2, sep = " ")
bigram_counts_per_book <- bigrams_united %>%
count(libro, bigram, sort = TRUE)
#visualizzazzione dei bigrammi
p_bigrams_parte1 <- bigram_counts_per_book %>%
filter(libro %in% levels(libro)[1:8]) %>%
group_by(libro) %>%
top_n(10, n) %>%
ungroup() %>%
mutate(bigram = reorder_within(bigram, n, libro)) %>%
ggplot(aes(bigram, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most frequent bigrams per book",
x = NULL,
y = "Count") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
p_bigrams_parte2 <- bigram_counts_per_book %>%
filter(libro %in% levels(libro)[9:15]) %>%
group_by(libro) %>%
top_n(10, n) %>%
ungroup() %>%
mutate(bigram = reorder_within(bigram, n, libro)) %>%
ggplot(aes(bigram, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most frequent bigrams per book",
x = NULL,
y = "Count") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
print(p_bigrams_parte1)
print(p_bigrams_parte2)
# 2. TRIGRAMMI
trigrams <- dfM %>%
unnest_tokens(trigram, text, token = "ngrams", n = 3) %>%
filter(!is.na(trigram)) %>%
separate(trigram, c("word1", "word2", "word3"), sep = " ") %>%
filter(!word1 %in% stop_words$word,
!word2 %in% stop_words$word,
!word3 %in% stop_words$word) %>%
unite(trigram, word1, word2, word3, sep = " ")
trigram_counts_per_book <- trigrams %>%
count(libro, trigram, sort = TRUE)
cat("\n=== Top 20 trigrams (corpus) ===\n")
##
## === Top 20 trigrams (corpus) ===
trigrams %>%
count(trigram, sort = TRUE) %>%
print(n = 20)
## # A tibble: 45,778 × 2
## trigram n
## <chr> <int>
## 1 yellow submarine boy 64
## 2 white subaru forester 61
## 3 boy named crow 40
## 4 fuka eri nodded 32
## 5 literature 978 0 27
## 6 nhk fee collector 27
## 7 ten thousand yen 27
## 8 middle aged woman 22
## 9 fiction literature 978 20
## 10 fuka eri shook 19
## 11 wild sheep chase 18
## 12 yellow submarine parka 18
## 13 978 0 375 16
## 14 mercury vapor lamp 16
## 15 978 0 679 15
## 16 head librarian's office 15
## 17 le mal du 15
## 18 red vinyl hat 15
## 19 rewriting air chrysalis 15
## 20 hundred thousand yen 14
## # ℹ 45,758 more rows
#visualizzazione istogramma
p_trigrams_parte1 <- trigram_counts_per_book %>%
filter(libro %in% levels(libro)[1:8]) %>%
group_by(libro) %>%
top_n(5, n) %>%
ungroup() %>%
mutate(trigram = reorder_within(trigram, n, libro)) %>%
ggplot(aes(trigram, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most frequent trigrams per book",
x = NULL,
y = "Count") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
p_trigrams_parte2 <- trigram_counts_per_book %>%
filter(libro %in% levels(libro)[9:15]) %>%
group_by(libro) %>%
top_n(5, n) %>%
ungroup() %>%
mutate(trigram = reorder_within(trigram, n, libro)) %>%
ggplot(aes(trigram, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most frequent trigrams per book",
x = NULL,
y = "Count") +
theme_minimal(base_size = 10) +
theme(
strip.text = element_text(size = 9, face = "bold"),
axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 7)
)
print(p_trigrams_parte1)
print(p_trigrams_parte2)
# 3. TF-IDF DEI BIGRAMMI
bigram_tf_idf <- bigrams_united %>%
count(libro, bigram) %>%
bind_tf_idf(bigram, libro, n) %>%
arrange(desc(tf_idf))
cat("\n=== Top bigrams by tf-idf ===\n")
##
## === Top bigrams by tf-idf ===
print(bigram_tf_idf, n = 20)
## # A tibble: 141,070 × 6
## libro bigram n tf idf tf_idf
## <fct> <chr> <int> <dbl> <dbl> <dbl>
## 1 12-1Q84 fuka eri 712 0.0179 2.71 0.0485
## 2 10-Kafka On The Shore miss saeki 193 0.0124 2.71 0.0337
## 3 8-The Wind-Up Bird Chronicle noboru wataya 182 0.00939 2.71 0.0254
## 4 8-The Wind-Up Bird Chronicle creta kano 165 0.00852 2.71 0.0231
## 5 15-The City and Its Uncertain yellow submarine 102 0.00807 2.71 0.0219
## 6 12-1Q84 air chrysalis 317 0.00798 2.71 0.0216
## 7 14-Killing Commendatore tomohiko amada 149 0.00780 2.71 0.0211
## 8 11-After Dark eri asai 39 0.00777 2.71 0.0210
## 9 3-A Wild Sheep Chase sheep professor 70 0.00841 2.01 0.0169
## 10 8-The Wind-Up Bird Chronicle malta kano 119 0.00614 2.71 0.0166
## 11 10-Kafka On The Shore johnnie walker 85 0.00548 2.71 0.0148
## 12 6-Dance Dance Dance dolphin hotel 80 0.00732 2.01 0.0148
## 13 15-The City and Its Uncertain submarine boy 64 0.00507 2.71 0.0137
## 14 9-Sputnik Sweetheart ferris wheel 29 0.00479 2.71 0.0130
## 15 2-Pinball, 1973 pinball machines 18 0.00460 2.71 0.0125
## 16 10-Kafka On The Shore colonel sanders 66 0.00425 2.71 0.0115
## 17 1-Hear the Wind Sing california girls 9 0.00410 2.71 0.0111
## 18 14-Killing Commendatore tomohiko amada's 75 0.00393 2.71 0.0106
## 19 2-Pinball, 1973 switch panel 20 0.00512 2.01 0.0103
## 20 14-Killing Commendatore killing commendatore 95 0.00498 2.01 0.0100
## # ℹ 141,050 more rows
# Visualizzazione tf-idf bigrammi
p_bigram_tfidf_1 <- bigram_tf_idf %>%
filter(libro %in% levels(libro)[1:8]) %>%
group_by(libro) %>%
top_n(12, tf_idf) %>%
ungroup() %>%
mutate(bigram = reorder_within(bigram, tf_idf, libro)) %>%
ggplot(aes(bigram, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive bigrams per book (tf-idf)",
x = NULL, y = "tf-idf") +
theme_minimal(base_size = 9) +
theme(
axis.text.y = element_text(size = 6),
strip.text = element_text(size = 8, face = "bold")
)
p_bigram_tfidf_2 <- bigram_tf_idf %>%
filter(libro %in% levels(libro)[9:15]) %>%
group_by(libro) %>%
top_n(12, tf_idf) %>%
ungroup() %>%
mutate(bigram = reorder_within(bigram, tf_idf, libro)) %>%
ggplot(aes(bigram, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive bigrams per book (tf-idf)",
x = NULL, y = "tf-idf") +
theme_minimal(base_size = 9) +
theme(
axis.text.y = element_text(size = 6),
strip.text = element_text(size = 8, face = "bold")
)
print(p_bigram_tfidf_1)
print(p_bigram_tfidf_2)
# 4. GRAFI DEI BIGRAMMI
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))
# Grafo complessivo
cat("\n=== Creating bigram network graph ===\n")
##
## === Creating bigram network graph ===
bigram_graph <- bigram_counts %>%
filter(n > 50) %>%
as_tbl_graph()
p_graph_all <- ggraph(bigram_graph, layout = "fr") +
geom_edge_link(aes(edge_alpha = n),
show.legend = FALSE,
arrow = a,
end_cap = circle(.07, 'inches')) +
geom_node_point(color = "steelblue", size = 3) +
geom_node_text(aes(label = name),
vjust = 1.5, hjust = 1,
size = 3, check_overlap = TRUE) +
theme_void() +
labs(title = "Bigram network — Murakami corpus (n > 50)")
print(p_graph_all)
# Grafi PER LIBRO
libri_esempio <- c("5-Norwegian wood",
"6-Dance Dance Dance",
"11-After Dark",
"12-1Q84")
for (book_title in libri_esempio) {
cat("\n--- Bigram graph:", book_title, "---\n")
book_bigrams <- bigrams_filtered %>%
filter(libro == book_title) %>%
count(word1, word2, sort = TRUE)
book_graph <- book_bigrams %>%
filter(n > 10) %>%
as_tbl_graph()
p <- ggraph(book_graph, layout = "fr") +
geom_edge_link(aes(edge_alpha = n),
show.legend = FALSE,
arrow = a,
end_cap = circle(.07, 'inches')) +
geom_node_point(color = "lightblue", size = 4) +
geom_node_text(aes(label = name),
vjust = 1.5, hjust = 1,
size = 3.5, check_overlap = TRUE) +
theme_void() +
labs(title = paste("Bigram network —", book_title))
print(p)
}
##
## --- Bigram graph: 5-Norwegian wood ---
##
## --- Bigram graph: 6-Dance Dance Dance ---
##
## --- Bigram graph: 11-After Dark ---
##
## --- Bigram graph: 12-1Q84 ---
# 5. CO-OCCORRENZE
# Co-occorrenze complessiva
section_words <- libri_tidy %>%
mutate(section = row_number() %/% 10) %>%
filter(section > 0)
word_pairs_raw <- section_words %>%
pairwise_count(word, section, sort = TRUE, upper = FALSE)
cat("\n=== Top 20 word co-occurrences (deduplicated) ===\n")
##
## === Top 20 word co-occurrences (deduplicated) ===
print(word_pairs_raw, n = 20)
## # A tibble: 1,861,590 × 3
## item1 item2 n
## <chr> <chr> <dbl>
## 1 eri fuka 606
## 2 head shook 495
## 3 eyes closed 339
## 4 eri tengo 308
## 5 tengo fuka 304
## 6 air chrysalis 280
## 7 time tengo 235
## 8 wind bird 233
## 9 time people 217
## 10 time eyes 214
## 11 aomame tengo 213
## 12 time left 201
## 13 time day 199
## 14 time head 193
## 15 time world 193
## 16 time life 185
## 17 time looked 184
## 18 eyes looked 180
## 19 miss saeki 176
## 20 time mind 172
## # ℹ 1,861,570 more rows
#grafo complessivo
p_cooc_all <- word_pairs_raw %>%
filter(n > 50) %>%
as_tbl_graph(directed = FALSE) %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = n), show.legend = FALSE) +
geom_node_point(color = "steelblue", size = 3) +
geom_node_text(aes(label = name),
repel = TRUE, size = 3,
max.overlaps = 15) +
theme_void() +
labs(title = "Word co-occurrence network — Murakami corpus (n > 50)")
print(p_cooc_all)
## Warning: ggrepel: 84 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
# Co-occorrenze PER LIBRO
for (book_title in libri_esempio) {
cat("\n--- Co-occurrence:", book_title, "---\n")
book_sections <- libri_tidy %>%
filter(libro == book_title) %>%
mutate(section = row_number() %/% 10) %>%
filter(section > 0)
book_pairs_raw <- book_sections %>%
pairwise_count(word, section, sort = TRUE)
book_pairs <- book_pairs_raw %>%
mutate(
word_a = if_else(item1 < item2, item1, item2),
word_b = if_else(item1 < item2, item2, item1)
) %>%
group_by(word_a, word_b) %>%
summarise(n = sum(n), .groups = "drop") %>%
rename(item1 = word_a, item2 = word_b) %>%
arrange(desc(n))
book_pairs_top <- book_pairs %>%
slice_max(n, n = 40)
p <- book_pairs_top %>%
filter(n > 15) %>% #
as_tbl_graph(directed = FALSE) %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = n, edge_width = n),
show.legend = FALSE,
color = "gray70") +
geom_node_point(color = "steelblue", size = 5) +
geom_node_text(aes(label = name),
repel = TRUE,
size = 4,
max.overlaps = 20,
force = 2,
box.padding = 0.5) +
theme_void() +
labs(title = paste("Co-occurrence network —", book_title),
subtitle = "(Top 40 pairs, n > 15)")
print(p)
}
##
## --- Co-occurrence: 5-Norwegian wood ---
## Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
## ℹ Please use the `transform` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
##
## --- Co-occurrence: 6-Dance Dance Dance ---
##
## --- Co-occurrence: 11-After Dark ---
##
## --- Co-occurrence: 12-1Q84 ---
# 6. CORRELAZIONI (PHI COEFFICIENT)
for (book_title in libri_esempio) {
cat("\n--- Correlations:", book_title, "---\n")
book_sections <- libri_tidy %>%
filter(libro == book_title) %>%
mutate(section = row_number() %/% 10) %>%
filter(section > 0)
# Filtra parole frequenti (almeno 20 occorrenze)
word_cors <- book_sections %>%
group_by(word) %>%
filter(n() >= 20) %>%
ungroup() %>%
pairwise_cor(word, section, sort = TRUE)
# Grafo correlazioni
p <- word_cors %>%
filter(correlation > .20) %>%
as_tbl_graph(directed = FALSE) %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = correlation),
show.legend = FALSE) +
geom_node_point(color = "lightblue", size = 4) +
geom_node_text(aes(label = name),
repel = TRUE, size = 3.5,
max.overlaps = 15) +
theme_void() +
labs(title = paste("Word correlation network —", book_title))
print(p)
}
##
## --- Correlations: 5-Norwegian wood ---
##
## --- Correlations: 6-Dance Dance Dance ---
##
## --- Correlations: 11-After Dark ---
##
## --- Correlations: 12-1Q84 ---
L’analisi di bigrammi, trigrammi e co-occorrenze mostra con chiarezza che la narrativa di Murakami non è costruita solo a livello tematico, ma anche attraverso formule linguistiche ricorrenti. I bigrammi sono coppie di parole consecutive che, analizzate insieme, rivelano pattern narrativi e stilistici e i bigrammi più frequenti nel corpus confermano la centralità dei nomi composti dei personaggi – Fuka Eri (712), Miss Saeki, Noboru Wataya, Dolphin Hotel – che funzionano come veri e propri nuclei semantici. I nomi diventano presenze rituali e la loro ripetizione costruisce familiarità, ossessione e identità.
Accanto ai nomi emergono espressioni corporee e temporali estremamente regolari: shook head, closed eyes, deep breath (141), ten minutes (109), time ago (105). Il corpo, in Murakami, è il primo strumento narrativo: i personaggi non spiegano ciò che provano, lo manifestano attraverso gesti minimi e ripetitivi. Allo stesso tempo, il tempo è quasi sempre misurato, cronometrico, mai astratto. Questo ancoraggio numerico e fisico rende credibile anche il fantastico, creando quell’iperrealismo che permette all’assurdo di apparire naturale.
Quando si osservano i bigrammi distintivi per romanzo, emergono identità narrative molto nette. Norwegian Wood è l’unico testo in cui compaiono con forza relazioni familiari e riferimenti quotidiani concreti( negozi di dischi, domeniche mattina, figure paterne). 1Q84, al contrario, costruisce universi simbolici autonomi, popolati da personaggi-metafora e oggetti magici (La ripetizione ossessiva di “fuka eri” riflette il suo ruolo di enigma irrisolvibile attorno a cui ruota la narrazione). Dance Dance Dance ruota ossessivamente attorno a un unico spazio, l’hotel, che diventa un labirinto psicologico, una metafora dell’inconscio.
I trigrammi rafforzano questa impressione. Murakami utilizza vere e proprie frasi-pattern che condensano personaggi, oggetti e concetti. Alter ego come boy named Crow, soprannomi pop come yellow submarine boy, o descrizioni iper-specifiche come white Subaru Forester mostrano una scrittura che combina cultura pop, precisione maniacale e simbolismo. Anche qui il gesto corporeo ritorna come forma primaria di comunicazione, Fuka-Eri che annuisce o scuote la testa rappresenta una soggettività che si esprime senza parole.
I grafi delle co-occorrenze mostrano quali parole compaiono frequentemente insieme nello stesso contesto. I testi realistici, come Norwegian Wood, presentano grafi più lineari e sparsi, mentre quelli fantastici (1Q84) generano reti dense, multi-centrate, quasi labirintiche. In tutti i casi, però, emerge un core lessicale condiviso (tempo, sguardo, corpo, mondo) attorno al quale si organizzano costellazioni specifiche di ogni romanzo.
L’analisi delle correlazioni tramite coefficiente phi consente di andare oltre la semplice frequenza misurando l’affinità statistica tra coppie di parole, cioè coppie di termini che tendono a comparire insieme in modo sistematico e non casuale. Norwegian Wood è dominato da correlazioni quotidiane e rituali, che ancorano la narrazione a un realismo emotivo. In Dance Dance Dance prevalgono legami spaziali e architettonici, con l’hotel come centro simbolico dell’alienazione urbana. 1Q84 mostra invece correlazioni simboliche e meta-narrative, dove nomi, oggetti e concetti costruiscono mondi autonomi e riflettono sulla natura stessa della finzione.
In questo contesto After Dark rappresenta un caso limite. Ambientato interamente in una sola notte, i grafi dei bigrammi sono frammentati. Non emergono luoghi mitici ricorrenti, né nomi ripetuti in modo ossessivo. After Dark è costruito come una sequenza di scene brevi, quasi cinematografiche, osservate da una “telecamera” impersonale.
La co-occorrenza delle parole rivela una forte coerenza tematica. Mari è l’unico vero hub del romanzo, è il punto di vista mobile che attraversa la notte e incontra personaggi diversi, ognuno dei quali resta confinato nel proprio episodio. Eri invece, pur essendo centrale sul piano simbolico, rimane periferica sul piano relazionale perché dorme. Il suo mondo è quello dell’inconscio ed mediato dalla televisione che rappresenta l’unico vero elemento fantastico del romanzo. La fortissima correlazione tra tv e screen conferma che la TV non è un oggetto, ma un portale tra realtà e altrove.
Un altro elemento dominante è il tempo. After Dark è scandito minuto per minuto: i numeri emergono isolati nei grafi, come se il tempo stesso fosse un personaggio silenzioso che sorveglia la narrazione. A differenza di 1Q84, dove il tempo è filosofico e metafisico, qui è materiale, implacabile, misurabile. Il risultato è una tensione costante tra movimento e immobilità. Mari vaga nella città, Eri resta immobile nel sonno.
Gli oggetti quotidiani – una chicken salad, una jacket pocket, un tavolo – assumono un peso sproporzionato, diventando ancore di realtà in un’atmosfera sospesa.
Le correlazioni sono deboli, disperse, episodiche. Non esiste una vera rete unificante, ma una serie di vignette notturne che si sfiorano senza fondersi.
# 1. LEXICON DISPONIBILI
# - bing: sentiment binario (positive/negative)
# - AFINN: score numerico da -5 a +5
# - nrc: 8 emozioni base + 2 poli di sentiment
get_sentiments("bing")
## # A tibble: 6,786 × 2
## word sentiment
## <chr> <chr>
## 1 2-faces negative
## 2 abnormal negative
## 3 abolish negative
## 4 abominable negative
## 5 abominably negative
## 6 abominate negative
## 7 abomination negative
## 8 abort negative
## 9 aborted negative
## 10 aborts negative
## # ℹ 6,776 more rows
get_sentiments("afinn")
## # A tibble: 2,477 × 2
## word value
## <chr> <dbl>
## 1 abandon -2
## 2 abandoned -2
## 3 abandons -2
## 4 abducted -2
## 5 abduction -2
## 6 abductions -2
## 7 abhor -3
## 8 abhorred -3
## 9 abhorrent -3
## 10 abhors -3
## # ℹ 2,467 more rows
get_sentiments("nrc")
## # A tibble: 13,872 × 2
## word sentiment
## <chr> <chr>
## 1 abacus trust
## 2 abandon fear
## 3 abandon negative
## 4 abandon sadness
## 5 abandoned anger
## 6 abandoned fear
## 7 abandoned negative
## 8 abandoned sadness
## 9 abandonment anger
## 10 abandonment fear
## # ℹ 13,862 more rows
# 2. PAROLE DI GIOIA PER OGNI LIBRO
#parole di joy per il dataframe complessivo
nrc_joy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
# Parole di gioia più frequenti per libro
libri_tidy %>%
filter(libro %in% levels(libro)[1:8]) %>%
inner_join(nrc_joy) %>%
count(libro, word, sort = TRUE) %>%
group_by(libro) %>%
slice_max(n, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, libro)) %>%
ggplot(aes(word, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most common joy words per book (NRC lexicon)",
x = NULL,
y = "Count")
## Joining with `by = join_by(word)`
libri_tidy %>%
filter(libro %in% levels(libro)[9:15]) %>%
inner_join(nrc_joy) %>%
count(libro, word, sort = TRUE) %>%
group_by(libro) %>%
slice_max(n, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, libro)) %>%
ggplot(aes(word, n, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Most common joy words per book (NRC lexicon)",
x = NULL,
y = "Count")
## Joining with `by = join_by(word)`
# 3. SENTIMENT NEL TEMPO PER OGNI LIBRO
murakami_sentiment <- libri_tidy %>%
inner_join(get_sentiments("bing")) %>%
count(libro, index = linea %/% 80, sentiment) %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
mutate(sentiment = positive - negative)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
murakami_sentiment
## # A tibble: 17 × 5
## libro index negative positive sentiment
## <fct> <dbl> <int> <int> <int>
## 1 1-Hear the Wind Sing 0 650 317 -333
## 2 2-Pinball, 1973 0 977 537 -440
## 3 3-A Wild Sheep Chase 0 2184 1164 -1020
## 4 4-Hard Boiled Wonderland and the End of th… 0 3291 1696 -1595
## 5 5-Norwegian wood 0 3041 2031 -1010
## 6 6-Dance Dance Dance 0 3623 2160 -1463
## 7 7-South of the Border West of the Sun 0 1395 924 -471
## 8 8-The Wind-Up Bird Chronicle 0 7091 3371 -3720
## 9 9-Sputnik Sweetheart 0 1456 1107 -349
## 10 10-Kafka On The Shore 0 4790 2628 -2162
## 11 11-After Dark 0 1160 637 -523
## 12 12-1Q84 0 9676 6324 -3352
## 13 12-1Q84 1 919 503 -416
## 14 13-Colorless Tsukuru Tazaki 0 2025 1653 -372
## 15 14-Killing Commendatore 0 5739 3742 -1997
## 16 15-The City and Its Uncertain 0 3455 2345 -1110
## 17 15-The City and Its Uncertain 1 128 85 -43
# Visualizzazione sentiment nel tempo per ogni libro
ggplot(murakami_sentiment, aes(index, sentiment, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free_x") +
labs(title = "Sentiment over time per book (Bing lexicon)",
x = "Section of book",
y = "Sentiment (positive - negative)")
# 4. PAROLE CHE CONTRIBUISCONO MAGGIORMENTE AL SENTIMENT
bing_word_counts <- libri_tidy %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup()
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
bing_word_counts
## # A tibble: 3,860 × 3
## word sentiment n
## <chr> <chr> <int>
## 1 hard negative 1282
## 2 dark negative 919
## 3 pretty positive 801
## 4 lost negative 787
## 5 bad negative 714
## 6 strange negative 706
## 7 love positive 701
## 8 cold negative 673
## 9 top positive 635
## 10 slowly negative 597
## # ℹ 3,850 more rows
# Visualizzazione top 10 parole per sentiment nel corpus complessivo
bing_word_counts %>%
group_by(sentiment) %>%
top_n(10) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(title = "Words contributing most to sentiment — Murakami corpus",
y = "Contribution to sentiment",
x = NULL) +
coord_flip()
## Selecting by n
# 5. PAROLE CHE CONTRIBUISCONO AL SENTIMENT PER LIBRO
libri_tidy %>%
filter(libro %in% levels(libro)[1:8]) %>%
inner_join(get_sentiments("bing")) %>%
count(libro, word, sentiment, sort = TRUE) %>%
group_by(libro, sentiment) %>%
top_n(5) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, libro)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Top sentiment words per book",
y = "Count",
x = NULL)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 142662 of `x` matches multiple rows in `y`.
## ℹ Row 4530 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
## Selecting by n
libri_tidy %>%
filter(libro %in% levels(libro)[9:15]) %>%
inner_join(get_sentiments("bing")) %>%
count(libro, word, sentiment, sort = TRUE) %>%
group_by(libro, sentiment) %>%
top_n(5) %>%
ungroup() %>%
mutate(word = reorder_within(word, n, libro)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, scales = "free", ncol = 3) +
coord_flip() +
scale_x_reordered() +
labs(title = "Top sentiment words per book",
y = "Count",
x = NULL)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 86992 of `x` matches multiple rows in `y`.
## ℹ Row 3857 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
## "many-to-many"` to silence this warning.
## Selecting by n
# 6. CAPITOLI PIU' NEGATIVI PER OGNI LIBRO
# Definisco i libri da analizzare
libri_con_capitoli <- c(
"1-Hear the Wind Sing",
"2-Pinball, 1973",
"7-South of the Border West of the Sun",
"4-Hard Boiled Wonderland and the End of the World",
"9-Sputnik Sweetheart",
"10-Kafka On The Shore",
"14-Killing Commendatore"
)
# Crea dataset con colonna chapter
libri_con_chapter <- dfM %>%
filter(libro %in% libri_con_capitoli) %>%
group_by(libro) %>%
mutate(chapter = case_when(
# Gruppo 1: Solo numero all'inizio (1, 2, 3...)
libro %in% c("1-Hear the Wind Sing",
"2-Pinball, 1973",
"4-Hard Boiled Wonderland and the End of the World",
"14-Killing Commendatore") ~
cumsum(str_detect(text, regex("^[0-9]+", ignore_case = TRUE))),
# Gruppo 2: Numero attaccato a parola (1My, 2In, 15I...)
libro == "7-South of the Border West of the Sun" ~
cumsum(str_detect(text, regex("^[0-9]+[A-Z]", ignore_case = FALSE))),
# Gruppo 3: "CHAPTER" seguito da numero (CHAPTER 1, CHAPTER 16...)
libro %in% c("9-Sputnik Sweetheart", "10-Kafka On The Shore") ~
cumsum(str_detect(text, regex("^chapter [0-9]+", ignore_case = TRUE))),
TRUE ~ 0
)) %>%
ungroup()
cat("\n=== Capitoli identificati per libro ===\n")
##
## === Capitoli identificati per libro ===
libri_con_chapter %>%
group_by(libro) %>%
summarise(chapters = max(chapter)) %>%
arrange(desc(chapters)) %>%
print()
## # A tibble: 7 × 2
## libro chapters
## <fct> <dbl>
## 1 14-Killing Commendatore 64
## 2 10-Kafka On The Shore 49
## 3 1-Hear the Wind Sing 40
## 4 4-Hard Boiled Wonderland and the End of the World 40
## 5 2-Pinball, 1973 26
## 6 9-Sputnik Sweetheart 16
## 7 7-South of the Border West of the Sun 15
# Tokenizza solo questi libri
libri_chapter_tidy <- libri_con_chapter %>%
unnest_tokens(word, text) %>%
anti_join(stop_words) %>%
filter(chapter > 0)
## Joining with `by = join_by(word)`
# Analisi capitoli più negativi
bingnegative <- get_sentiments("bing") %>%
filter(sentiment == "negative")
# Conta parole totali per libro e capitolo
wordcounts <- libri_chapter_tidy %>%
group_by(libro, chapter) %>%
summarize(words = n(), .groups = "drop")
# Trova i capitoli più negativi per libro
capitoli_negativi <- libri_chapter_tidy %>%
semi_join(bingnegative) %>%
group_by(libro, chapter) %>%
summarize(negativewords = n(), .groups = "drop") %>%
left_join(wordcounts, by = c("libro", "chapter")) %>%
mutate(ratio = negativewords / words) %>%
group_by(libro) %>%
slice_max(ratio, n = 1) %>%
ungroup() %>%
arrange(desc(ratio))
## Joining with `by = join_by(word)`
capitoli_negativi
## # A tibble: 7 × 5
## libro chapter negativewords words ratio
## <fct> <dbl> <int> <int> <dbl>
## 1 1-Hear the Wind Sing 6 17 72 0.236
## 2 4-Hard Boiled Wonderland and the End of th… 32 101 651 0.155
## 3 14-Killing Commendatore 51 200 1508 0.133
## 4 10-Kafka On The Shore 29 93 771 0.121
## 5 2-Pinball, 1973 15 51 476 0.107
## 6 7-South of the Border West of the Sun 13 70 711 0.0985
## 7 9-Sputnik Sweetheart 11 99 1116 0.0887
# Visualizzazione capitoli più negativi
capitoli_negativi %>%
mutate(libro = fct_reorder(libro, ratio)) %>%
ggplot(aes(libro, ratio, fill = libro)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = paste("Ch.", chapter)), hjust = -0.1, size = 3) +
coord_flip() +
labs(title = "Most negative chapter per book",
subtitle = "Only books with identifiable chapter structure",
x = NULL,
y = "Ratio of negative words") +
theme_bw()
# 7. SENTIMENT MEDIO PER LIBRO (AFINN)
libri_tidy %>%
inner_join(get_sentiments("afinn")) %>%
group_by(libro) %>%
summarize(mean_sentiment = mean(value)) %>%
arrange(mean_sentiment) %>%
mutate(libro = fct_reorder(libro, mean_sentiment)) %>%
ggplot(aes(libro, mean_sentiment, fill = mean_sentiment > 0)) +
geom_col(show.legend = FALSE) +
coord_flip() +
labs(title = "Average sentiment per book (AFINN lexicon)",
x = NULL,
y = "Mean sentiment score")
## Joining with `by = join_by(word)`
L’analisi delle joy words basata sul lexicon NRC mostra con chiarezza che nei testi di Murakami, la felicità non è mai un’emozione esplicitamente dichiarata, ma anzi come un concetto debole, indiretto e ambiguo. Le parole classificate come “gioiose” non rimandano a stati di felicità pienamente vissuti, ma a qualità estetiche, gesti minimi, momenti sospesi o forme di accettazione passiva dell’esperienza.
La caratteristica però non è tanto la poca presenza di termini positivi, quanto l’assenza sistematica del termine “felicità” esplicito. Parole come happy, happiness o joy risultano rare o del tutto assenti dalle posizioni più frequenti, confermando che Murakami evita consapevolmente la nominazione diretta della felicità. Questo dato è coerente con la sua poetica, che oscilla stabilmente tra neutralità emotiva, malinconia e oscurità, senza mai approdare a una gioia pienamente affermata.
Le parole ricorrenti identificate come “gioiose” (pretty, love, smile, fine) hanno una bassa intensità emotiva, un forte ancoraggio al corpo o al quotidiano e una totale assenza di euforia. Si tratta di termini che descrivono stati lievi, momentanei, spesso privi di una dimensione trasformativa. Questo suggerisce che la gioia, nel corpus murakamiano, sia attenuata, transitoria e frequentemente retrospettiva (un residuo fragile di un’esperienza già perduta).
Questa concezione emerge con particolare chiarezza in Norwegian Wood, il romanzo che statisticamente contiene il maggior numero di parole di gioia (ma che non è il più positivo). Qui love (con oltre 100 occorrenze) domina il lessico positivo, insieme a pretty e beautiful, cosa che però entra in completa contraddizione con il tono complessivo del romanzo che resta profondamente triste. Si parla d’amore mentre tutto va in pezzi. in questo contesto love non è promessa di felicità ma accompagna il trauma (suicidi, depressione, lutto) ed è considerato come un preludio alla perdita.
In 1Q84, invece, pretty domina il lessico (circa 250 occorrenze), soprattutto in riferimento a Fuka-Eri, figura bellissima e inafferrabile, mentre love convive con parole come friend e fine. Non c’è passione travolgente, ma una felicità possibile, costruita lentamente, quasi con diffidenza. Questo tra l’altro è l’unico romanzo che mostra una vera tendenza positiva finale concludendo con il ricongiungimento di Tengo e Aomame. La dinamica può essere vista come una sorta di fiaba adulta dopo centinaia di pagine di oscurità.
Al polo opposto si colloca The Wind-Up Bird Chronicle, dove le parole di gioia sono decisamente poche rispetto alla lunghezza del testo (max ~150). Qui la violenza storica, il trauma e la discesa nell’inconscio sovrastano qualsiasi spiraglio luminoso. Il sentimento resta costantemente negativo, confermando che si tratta del romanzo più cupo e disturbante di Murakami. Anche quando compaiono termini positivi, questi vengono assorbiti da un contesto di guerra, perdita e disintegrazione personale.
Guardando l’andamento del sentiment nel tempo, tutti i romanzi mostrano un sentiment che fluttua tra -0.25 e +0.25, raramente superando questi limiti e questo può essere visto come un assenza sistematica di ambiguità emotiva. Luce e ombra coesistono senza mai annullarsi. After Dark, ad esempio, resta quasi neutro per tutta la sua durata e infatti può essere considerato come un romanzo che osserva, non giudica, che registra la notte senza attribuirle un significato morale.
Le parole negative più frequenti – hard, dark, lost, strange – definiscono il cuore emotivo dell’opera murakamiana. hard (≈1280 occorrenze) indica difficoltà esistenziale, non male morale. La vita, nei suoi romanzi, non è malvagia, è difficile. Non c’è un nemico da combattere, ma una fatica continua dell’esistere. La coppia dark/darkness (≈1495 occorrenze) domina il lessico negativo, confermando l’ossessione per l’oscurità fisica e metaforica. Il rapporto 2:1 con light segnala una asimmetria strutturale. Lost esprime lo stato permanente dei protagonisti, sempre smarriti, senza mappe né direzioni chiare. Strange, infine, normalizza il surreale infatti di fronte a due lune nel cielo o a gatti che parlano, i personaggi non reagiscono con terrore, ma con un semplice “è strano”, accettando l’anomalia come parte del reale.
Le parole positive risultano profondamente ambigue. Termini come quiet o silent, classificati come positivi dai dizionari, in Murakami assumono spesso una tonalità inquietante. Il silenzio non è pace, ma vuoto; la quiete può essere sospensione prima di qualcosa di oscuro.
Nel complesso, la classifica finale del sentiment medio conferma che Murakami non scrive mai romanzi davvero felici. Anche il più “positivo”, Colorless Tsukuru Tazaki, supera di poco lo zero, suggerendo non tanto una gioia piena quanto una riconciliazione malinconica con il passato. Dai primi romanzi nichilisti fino alle opere più mature, l’autore sembra spostarsi non verso la felicità, ma verso una forma più gentile di tristezza.
Murakami rappresenta un equilibrio instabile tra luce e ombra, in cui la felicità non è mai gridata, ma sussurrata, spesso proprio nei momenti in cui sta per svanire.
# COSTRUZIONE DELLA RETE GLOBALE
cat("\n=== 1. GLOBAL LEXICAL NETWORK CONSTRUCTION ===\n\n")
##
## === 1. GLOBAL LEXICAL NETWORK CONSTRUCTION ===
# Calcola co-occorrenze doppie (pairwise count)
word_pairs <- section_words %>%
pairwise_count(word, section, sort = TRUE) %>%
filter(n > 100)
cat("Total word pairs (n > 100):", nrow(word_pairs), "\n")
## Total word pairs (n > 100): 158
cat("Sample pairs:\n")
## Sample pairs:
print(head(word_pairs, 10))
## # A tibble: 10 × 3
## item1 item2 n
## <chr> <chr> <dbl>
## 1 fuka eri 606
## 2 eri fuka 606
## 3 shook head 495
## 4 head shook 495
## 5 closed eyes 339
## 6 eyes closed 339
## 7 tengo eri 308
## 8 eri tengo 308
## 9 fuka tengo 304
## 10 tengo fuka 304
# Crea grafo non diretto
g <- word_pairs %>%
as_tbl_graph(directed = FALSE)
cat("\nNetwork size:\n")
##
## Network size:
cat("- Nodes (words):", vcount(g), "\n")
## - Nodes (words): 89
cat("- Edges (co-occurrences):", ecount(g), "\n")
## - Edges (co-occurrences): 158
cat("- Density:", round(edge_density(g), 4), "\n\n")
## - Density: 0.0403
#calcolo di tutte le componenti presenti
cat("\n=== 2. CONNECTED COMPONENTS ===\n\n")
##
## === 2. CONNECTED COMPONENTS ===
components_list <- components(g)
cat("Graph connectivity:\n")
## Graph connectivity:
cat("- Number of components:", components_list$no, "\n")
## - Number of components: 18
cat("- Size of largest component:", max(components_list$csize), "\n")
## - Size of largest component: 52
cat("- All component sizes:", components_list$csize, "\n\n")
## - All component sizes: 52 2 2 2 2 3 2 2 2 2 2 2 2 4 2 2 2 2
giant_component <- induced_subgraph(g, which(components_list$membership == which.max(components_list$csize)))
cat("Largest connected component:\n")
## Largest connected component:
cat("- Nodes:", vcount(giant_component), "\n")
## - Nodes: 52
cat("- Edges:", ecount(giant_component), "\n")
## - Edges: 118
cat("- Density:", round(edge_density(giant_component), 4), "\n\n")
## - Density: 0.089
# 2. DEGREE CENTRALITY
cat("\n=== 2. DEGREE CENTRALITY ===\n\n")
##
## === 2. DEGREE CENTRALITY ===
degree_igraph <- degree(g)
# Crea dataframe risultati
degree_df <- tibble(
word = V(g)$name,
degree = degree_igraph
) %>%
arrange(desc(degree))
cat("Top 20 words by degree centrality:\n")
## Top 20 words by degree centrality:
print(degree_df %>% head(20))
## # A tibble: 20 × 2
## word degree
## <chr> <dbl>
## 1 time 68
## 2 tengo 20
## 3 eyes 12
## 4 aomame 8
## 5 world 8
## 6 head 6
## 7 people 6
## 8 looked 6
## 9 fuka 4
## 10 eri 4
## 11 left 4
## 12 hand 4
## 13 kano 4
## 14 night 4
## 15 deep 4
## 16 middle 4
## 17 book 4
## 18 read 4
## 19 shook 2
## 20 closed 2
# Visualizzazione
degree_df %>%
head(20) %>%
mutate(word = reorder(word, degree)) %>%
ggplot(aes(word, degree)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Degree Centrality — Murakami Lexical Network",
subtitle = "Most connected words (lexical hubs)",
x = NULL, y = "Degree (number of co-occurring words)") +
theme_minimal()
# 3. CLOSENESS CENTRALITY
cat("\n\n=== 3. CLOSENESS CENTRALITY ===\n\n")
##
##
## === 3. CLOSENESS CENTRALITY ===
closeness_scores <- closeness(giant_component)
closeness_df <- tibble(
word = V(giant_component)$name,
closeness = closeness_scores
) %>%
arrange(desc(closeness))
cat("Top 20 words by closeness centrality:\n")
## Top 20 words by closeness centrality:
print(closeness_df %>% head(20))
## # A tibble: 20 × 2
## word closeness
## <chr> <dbl>
## 1 time 0.0143
## 2 tengo 0.00971
## 3 looked 0.00926
## 4 eyes 0.00917
## 5 aomame 0.00909
## 6 world 0.00901
## 7 people 0.00893
## 8 head 0.00862
## 9 night 0.00862
## 10 left 0.00840
## 11 hand 0.00840
## 12 day 0.00833
## 13 life 0.00833
## 14 mind 0.00833
## 15 told 0.00833
## 16 ago 0.00833
## 17 feel 0.00833
## 18 inside 0.00833
## 19 passed 0.00833
## 20 lot 0.00833
# Visualizzazione
closeness_df %>%
head(20) %>%
mutate(word = reorder(word, closeness)) %>%
ggplot(aes(word, closeness)) +
geom_col(fill = "coral") +
coord_flip() +
labs(title = "Closeness Centrality — Murakami Lexical Network",
subtitle = "Words with shortest average distance to others",
x = NULL, y = "Closeness") +
theme_minimal()
# 4. BETWEENNESS CENTRALITY
cat("\n\n=== 4. BETWEENNESS CENTRALITY ===\n\n")
##
##
## === 4. BETWEENNESS CENTRALITY ===
betweenness_scores <- betweenness(g)
betweenness_df <- tibble(
word = V(g)$name,
betweenness = betweenness_scores
) %>%
arrange(desc(betweenness))
cat("Top 20 words by betweenness centrality:\n")
## Top 20 words by betweenness centrality:
print(betweenness_df %>% head(20))
## # A tibble: 20 × 2
## word betweenness
## <chr> <dbl>
## 1 time 1158.
## 2 tengo 246.
## 3 eyes 239
## 4 head 99
## 5 aomame 99
## 6 night 98
## 7 world 50
## 8 deep 50
## 9 middle 50
## 10 looked 18
## 11 book 2
## 12 read 2
## 13 kano 1
## 14 fuka 0
## 15 eri 0
## 16 shook 0
## 17 closed 0
## 18 chrysalis 0
## 19 air 0
## 20 bird 0
# Visualizzazione
betweenness_df %>%
head(20) %>%
mutate(word = reorder(word, betweenness)) %>%
ggplot(aes(word, betweenness)) +
geom_col(fill = "darkgreen") +
coord_flip() +
labs(title = "Betweenness Centrality — Murakami Lexical Network",
subtitle = "Words that bridge different semantic regions",
x = NULL, y = "Betweenness") +
theme_minimal()
# Combina tutte le centralità
centrality_all <- degree_df %>%
left_join(closeness_df, by = "word") %>%
left_join(betweenness_df, by = "word")
# 5. NETWORK VISUALIZATION
cat("\n\n=== 5. NETWORK VISUALIZATION ===\n\n")
##
##
## === 5. NETWORK VISUALIZATION ===
set.seed(42)
coords <- layout_with_fr(g)
# Plot colorato per degree
plot(g, layout = coords,
vertex.size = sqrt(degree(g)) * 1.5,
vertex.color = "lightblue",
vertex.label = ifelse(degree(g) > quantile(degree(g), 0.95),
V(g)$name, NA),
vertex.label.cex = 0.7,
vertex.label.color = "black",
edge.width = 0.3,
edge.color = "gray80",
main = "Murakami Lexical Network — Node size = Degree")
# Plot con ggraph
set.seed(42)
ggraph(g, layout = "fr") +
geom_edge_link(alpha = 0.1, color = "gray70") +
geom_node_point(aes(size = degree(g), color = betweenness(g)), alpha = 0.7) +
geom_node_text(aes(label = name,
filter = degree(g) > quantile(degree(g), 0.95)),
repel = TRUE, size = 3, max.overlaps = 20) +
scale_color_viridis_c(option = "plasma") +
scale_size_continuous(range = c(1, 8)) +
theme_void() +
labs(title = "Murakami Lexical Network",
subtitle = "Node size = Degree | Color = Betweenness",
color = "Betweenness", size = "Degree")
# 6. COMMUNITY DETECTION
cat("\n\n=== 6. COMMUNITY DETECTION ===\n\n")
##
##
## === 6. COMMUNITY DETECTION ===
# Modularity matrix
m <- ecount(g)
k <- degree(g)
n <- vcount(g)
A <- as_adjacency_matrix(g, sparse = FALSE)
B <- A - (k %*% t(k)) / (2 * m)
cat("Modularity matrix properties:\n")
## Modularity matrix properties:
cat("- Sum of row 1:", round(sum(B[1,]), 10), "(should be ~0)\n")
## - Sum of row 1: 0 (should be ~0)
cat("- Sum of all elements:", round(sum(B), 10), "(should be ~0)\n\n")
## - Sum of all elements: 0 (should be ~0)
# Community detection con Louvain
communities <- cluster_louvain(g)
cat("Community detection results:\n")
## Community detection results:
cat("- Number of communities:", length(communities), "\n")
## - Number of communities: 22
cat("- Modularity Q:", round(modularity(communities), 4), "\n")
## - Modularity Q: 0.6781
cat("- Community sizes:", sizes(communities), "\n\n")
## - Community sizes: 12 3 7 2 27 2 2 2 3 2 3 2 2 2 2 2 2 4 2 2 2 2
# Top parole per community
communities_df <- tibble(
word = V(g)$name,
community = membership(communities),
degree = degree(g)
) %>%
group_by(community) %>%
arrange(desc(degree)) %>%
slice_head(n = 8)
cat("Top 8 words per community:\n")
## Top 8 words per community:
print(communities_df, n = 100)
## # A tibble: 66 × 3
## # Groups: community [22]
## word community degree
## <chr> <membrshp> <dbl>
## 1 tengo 1 20
## 2 aomame 1 8
## 3 world 1 8
## 4 people 1 6
## 5 fuka 1 4
## 6 eri 1 4
## 7 komatsu 1 2
## 8 dowager 1 2
## 9 head 2 6
## 10 shook 2 2
## 11 shake 2 2
## 12 eyes 3 12
## 13 looked 3 6
## 14 deep 3 4
## 15 closed 3 2
## 16 breath 3 2
## 17 narrowed 3 2
## 18 shut 3 2
## 19 chrysalis 4 2
## 20 air 4 2
## 21 time 5 68
## 22 left 5 4
## 23 hand 5 4
## 24 day 5 2
## 25 life 5 2
## 26 mind 5 2
## 27 told 5 2
## 28 ago 5 2
## 29 bird 6 2
## 30 wind 6 2
## 31 saeki 7 2
## 32 miss 7 2
## 33 wataya 8 2
## 34 noboru 8 2
## 35 kano 9 4
## 36 creta 9 2
## 37 malta 9 2
## 38 hoshino 10 2
## 39 nakata 10 2
## 40 night 11 4
## 41 middle 11 4
## 42 aged 11 2
## 43 cup 12 2
## 44 coffee 12 2
## 45 tomohiko 13 2
## 46 amada 13 2
## 47 front 14 2
## 48 door 14 2
## 49 phone 15 2
## 50 call 15 2
## 51 miu 16 2
## 52 sumire 16 2
## 53 shoulder 17 2
## 54 bag 17 2
## 55 book 18 4
## 56 read 18 4
## 57 reading 18 2
## 58 books 18 2
## 59 elementary 19 2
## 60 school 19 2
## 61 dolphin 20 2
## 62 hotel 20 2
## 63 clouds 21 2
## 64 sky 21 2
## 65 minutes 22 2
## 66 ten 22 2
# Visualizzazione communities
set.seed(42)
ggraph(g, layout = "fr") +
geom_edge_link(alpha = 0.05, color = "gray80") +
geom_node_point(aes(color = factor(membership(communities)),
size = degree(g)), alpha = 0.7) +
geom_node_text(aes(label = name,
filter = degree(g) > quantile(degree(g), 0.93)),
repel = TRUE, size = 3, max.overlaps = 15) +
scale_size_continuous(range = c(2, 8)) +
theme_void() +
labs(title = "Semantic Communities — Murakami Corpus",
subtitle = paste("Louvain algorithm | Modularity Q =",
round(modularity(communities), 3))) +
theme(legend.position = "none")
# 7. PROPRIETà PICCOLO MONDO
cat("\n\n=== 7. SMALL-WORLD PROPERTIES ===\n\n")
##
##
## === 7. SMALL-WORLD PROPERTIES ===
mean_dist <- mean_distance(giant_component, directed = FALSE)
diam <- diameter(giant_component)
n_nodes <- vcount(giant_component)
cat("Network properties:\n")
## Network properties:
cat("- Nodes:", n_nodes, "\n")
## - Nodes: 52
cat("- Mean distance:", round(mean_dist, 3), "\n")
## - Mean distance: 2.59
cat("- Diameter:", diam, "\n")
## - Diameter: 6
cat("- log(n):", round(log(n_nodes), 3), "\n")
## - log(n): 3.951
cat("- Mean distance / log(n):", round(mean_dist / log(n_nodes), 3), "\n\n")
## - Mean distance / log(n): 0.655
dist_table <- distance_table(giant_component)
dist_freq <- dist_table$res / sum(dist_table$res)
barplot(dist_freq,
names.arg = 1:length(dist_freq),
main = "Distribution of Distances — Largest Component",
xlab = "Distance", ylab = "Frequency",
col = "steelblue")
cat("avg distance ~",
round(mean_dist, 2), ", \n")
## avg distance ~ 2.59 ,
# 9. ANALISI COMPONENTI ISOLATE
cat("\n\n=== 9. ISOLATED COMPONENTS ===\n\n")
##
##
## === 9. ISOLATED COMPONENTS ===
for (i in 1:components_list$no) {
if (components_list$csize[i] < max(components_list$csize)) {
cat("\nComponent", i, "(size:", components_list$csize[i], ")\n")
comp_nodes <- V(g)$name[components_list$membership == i]
cat("Nodes:", paste(comp_nodes, collapse = ", "), "\n")
comp_graph <- induced_subgraph(g, which(components_list$membership == i))
if (ecount(comp_graph) > 0) {
edges_comp <- as_data_frame(comp_graph, what = "edges")
cat("Edges:\n")
print(edges_comp)
}
}
}
##
## Component 2 (size: 2 )
## Nodes: chrysalis, air
## Edges:
## from to n
## 1 chrysalis air 280
## 2 chrysalis air 280
##
## Component 3 (size: 2 )
## Nodes: bird, wind
## Edges:
## from to n
## 1 bird wind 233
## 2 bird wind 233
##
## Component 4 (size: 2 )
## Nodes: saeki, miss
## Edges:
## from to n
## 1 saeki miss 176
## 2 saeki miss 176
##
## Component 5 (size: 2 )
## Nodes: wataya, noboru
## Edges:
## from to n
## 1 wataya noboru 160
## 2 wataya noboru 160
##
## Component 6 (size: 3 )
## Nodes: creta, kano, malta
## Edges:
## from to n
## 1 creta kano 156
## 2 creta kano 156
## 3 kano malta 110
## 4 kano malta 110
##
## Component 7 (size: 2 )
## Nodes: hoshino, nakata
## Edges:
## from to n
## 1 hoshino nakata 154
## 2 hoshino nakata 154
##
## Component 8 (size: 2 )
## Nodes: cup, coffee
## Edges:
## from to n
## 1 cup coffee 141
## 2 cup coffee 141
##
## Component 9 (size: 2 )
## Nodes: tomohiko, amada
## Edges:
## from to n
## 1 tomohiko amada 125
## 2 tomohiko amada 125
##
## Component 10 (size: 2 )
## Nodes: front, door
## Edges:
## from to n
## 1 front door 122
## 2 front door 122
##
## Component 11 (size: 2 )
## Nodes: phone, call
## Edges:
## from to n
## 1 phone call 117
## 2 phone call 117
##
## Component 12 (size: 2 )
## Nodes: miu, sumire
## Edges:
## from to n
## 1 miu sumire 116
## 2 miu sumire 116
##
## Component 13 (size: 2 )
## Nodes: shoulder, bag
## Edges:
## from to n
## 1 shoulder bag 110
## 2 shoulder bag 110
##
## Component 14 (size: 4 )
## Nodes: book, read, reading, books
## Edges:
## from to n
## 1 book read 109
## 2 book read 109
## 3 book reading 103
## 4 book reading 103
## 5 read books 102
## 6 read books 102
##
## Component 15 (size: 2 )
## Nodes: elementary, school
## Edges:
## from to n
## 1 elementary school 107
## 2 elementary school 107
##
## Component 16 (size: 2 )
## Nodes: dolphin, hotel
## Edges:
## from to n
## 1 dolphin hotel 105
## 2 dolphin hotel 105
##
## Component 17 (size: 2 )
## Nodes: clouds, sky
## Edges:
## from to n
## 1 clouds sky 103
## 2 clouds sky 103
##
## Component 18 (size: 2 )
## Nodes: minutes, ten
## Edges:
## from to n
## 1 minutes ten 102
## 2 minutes ten 102
Per analizzare l’organizzazione strutturale del vocabolario murakamiano, abbiamo utilizzato la rete di co-occorrenze lessicali. La rete risultante comprende 89 nodi e 158 archi con una densità pari a 0.0403. Tale bassa densità indica che il lessico non forma una struttura uniformemente connessa, ma tende a organizzarsi in nuclei tematici distinti, separati da ampie regioni scarsamente collegate. La rete appare dunque globalmente sparsa, ma localmente densa.
La rete è composta da 18 componenti connesse, una delle quali emerge nettamente come componente gigante, con 52 nodi e 118 archi. Questa componente concentra la maggior parte delle connessioni e rappresenta il nucleo semantico globale del corpus che come abbiamo visto anche dall’analisi precedenti gira tutto intorno alla parola Time. Le restanti componenti sono di dimensioni molto ridotte (2–4 nodi) e risultano strutturalmente isolate dal resto della rete.
La densità della componente gigante (0.089) è più che doppia rispetto a quella dell’intera rete, segnalando una maggiore coesione interna.
La degree centrality identifica le parole che co-occorrono con il maggior numero di termini distinti, fungendo da hub lessicali.
La parola time emerge come hub assoluto, con un degree pari a 68, valore nettamente superiore a quello di qualsiasi altro nodo. Seguono tengo (20) ed eyes (12), mentre aomame, world, people, head e looked completano il nucleo delle parole più connesse. Questo insieme delinea un asse semantico stabile, incentrato su tempo, percezione, corpo ed esistenza, che come anche visto prima, attraversa trasversalmente l’intero corpus.
La closeness centrality misura quanto una parola sia mediamente vicina a tutte le altre in termini di distanza geodetica. Poiché la rete globale è sconnessa, la closeness è stata calcolata esclusivamente sulla componente gigante, evitando distorsioni dovute alla presenza di componenti isolate.
I risultati confermano time come nodo strutturalmente centrale anche in termini di distanza, La convergenza tra degree e closeness rafforza l’interpretazione di time come asse portante dell’architettura lessicale.
La betweenness centrality quantifica il numero di cammini minimi che attraversano un nodo, identificando le parole che fungono da ponti tra regioni semantiche diverse. Anche in questo caso, time domina nettamente con un valore pari a 1158, più del quadruplo del secondo classificato (tengo, 246).
Parole come eyes, head, night e world mostrano betweenness elevata pur avendo un degree relativamente contenuto, indicando che la loro importanza non risiede nel numero di connessioni, ma nella loro posizione strategica all’interno della rete. Al contrario, coppie di nomi propri come fuka e eri presentano betweenness nulla, rimanendo confinate in regioni semantiche locali senza contribuire alla connessione globale del grafo.
L’algoritmo di Louvain individua 22 comunità, con una modularità pari a 0.6781, valore che indica una struttura comunitaria fortemente marcata. Le connessioni all’interno delle comunità superano ampiamente quelle attese per caso, confermando la presenza di cluster semantici coerenti.
La comunità dominante è centrata su time e raccoglie termini esistenziali e temporali (life, mind, day, ago), configurandosi come il cuore filosofico del vocabolario. Altre comunità risultano fortemente legate a singoli romanzi o contesti narrativi, come il cluster di 1Q84 (tengo, aomame, fuka, eri), o il cluster percettivo dominato da eyes e dai verbi dello sguardo. Micro-comunità isolate, come book–read–reading–books, confermano la presenza di temi specifici e autosufficienti.
L’analisi delle proprietà di small-world, condotta sulla componente gigante, mostra una distanza media pari a 2.59 e un diametro pari a 6, a fronte di un valore log(𝑛)log(n) di circa 3.95. Il rapporto tra distanza media e log(𝑛)log(n) (≈ 0.65) indica che la rete presenta cammini sorprendentemente brevi.
Questo risultato suggerisce che il nucleo lessicale murakamiano è altamente navigabile. La distribuzione delle distanze mostra una netta concentrazione su valori bassi (2–3), tipica delle reti small-world reali.
Nelle componenti esterne si osservano numerose coppie di nomi propri (saeki–miss, wataya–noboru, hoshino–nakata, miu–sumire), che riflettono relazioni narrative specifiche e circoscritte.
L’analisi di rete mostra che il vocabolario murakamiano si organizza come una struttura small-world, caratterizzata da un nucleo compatto e altamente connesso dominato da poche parole-chiave (time, eyes, world), e da una costellazione di micro-comunità narrative isolate. La modularità elevata indica che ogni romanzo conserva una propria identità lessicale, pur condividendo un linguaggio concettuale trasversale.
In questo senso, la rete lessicale non è una semplice collezione di frequenze, ma una vera architettura semantica: il tempo, la percezione e l’esistenza fungono da assi portanti, mentre personaggi e contesti specifici formano isole narrative che arricchiscono, senza frammentare, l’universo murakamiano.
# PREPARAZIONE Tokenizzazione libri di confronto
altri_tidy <- dfA %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
## Joining with `by = join_by(word)`
#write_rds(altri_tidy, "others_books_tidy.rds")
# 1. CONFRONTO MURAKAMI VS ALTRI AUTORI
frequency <- bind_rows(
libri_tidy %>% mutate(author = "Murakami"),
altri_tidy %>% mutate(author = libro)
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
frequency
## # A tibble: 35,676 × 6
## word `1984` Murakami `The Great Gatsby` `The Long Goodbye`
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 aaronson 0.000226 NA NA NA
## 2 aback 0.0000565 0.0000338 NA NA
## 3 abandon 0.0000847 0.0000476 0.0000590 NA
## 4 abandoned 0.000113 0.000177 0.000118 NA
## 5 abasement 0.0000565 NA NA NA
## 6 abashed 0.0000282 NA NA NA
## 7 abbreviated 0.0000282 0.0000107 NA NA
## 8 abbreviating 0.0000565 0.00000154 NA NA
## 9 abbreviations 0.0000847 NA NA NA
## 10 aberrations 0.0000282 NA NA NA
## # ℹ 35,666 more rows
## # ℹ 1 more variable: `Trout Fishing in America` <dbl>
# Plot e correlazione per ogni autore di confronto
libri_confronto <- setdiff(names(frequency), c("word", "Murakami"))
for (libro in libri_confronto) {
p <- ggplot(frequency, aes(x = .data[[libro]], y = Murakami)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = paste("Murakami vs", libro),
x = libro,
y = "Murakami") +
theme_bw()
print(p)
cor_result <- cor.test(frequency[[libro]], frequency$Murakami)
cat("\nCorrelation Murakami -", libro, ":", round(cor_result$estimate, 3), "\n")
}
## Warning: Removed 28918 rows containing missing values or values outside the scale range
## (`geom_text()`).
##
## Correlation Murakami - 1984 : 0.586
## Warning: Removed 31140 rows containing missing values or values outside the scale range
## (`geom_text()`).
##
## Correlation Murakami - The Great Gatsby : 0.54
## Warning: Removed 29710 rows containing missing values or values outside the scale range
## (`geom_text()`).
##
## Correlation Murakami - The Long Goodbye : 0.705
## Warning: Removed 32092 rows containing missing values or values outside the scale range
## (`geom_text()`).
##
## Correlation Murakami - Trout Fishing in America : 0.371
# 2. CONFRONTI DIRETTI: 1Q84 vs 1984
freq_1q84_vs_1984 <- bind_rows(
libri_tidy %>%
filter(libro == "12-1Q84") %>%
mutate(author = "1Q84"),
altri_tidy %>%
filter(libro == "1984") %>%
mutate(author = "1984")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
# Plot
ggplot(freq_1q84_vs_1984, aes(x = `1984`, y = `1Q84`)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = "1Q84 (Murakami) vs 1984 (Orwell)",
x = "1984 — Orwell",
y = "1Q84 — Murakami") +
theme_bw()
## Warning: Removed 12876 rows containing missing values or values outside the scale range
## (`geom_text()`).
# Correlazione
cor.test(freq_1q84_vs_1984$`1984`, freq_1q84_vs_1984$`1Q84`)
##
## Pearson's product-moment correlation
##
## data: freq_1q84_vs_1984$`1984` and freq_1q84_vs_1984$`1Q84`
## t = 50.677, df = 5191, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.5568283 0.5932306
## sample estimates:
## cor
## 0.5753143
# 3. CONFRONTI DIRETTI: VENTO vs TROTA
freq_vento_vs_trota <- bind_rows(
libri_tidy %>%
filter(libro == "1-Hear the Wind Sing") %>%
mutate(author = "Hear the Wind Sing"),
altri_tidy %>%
filter(libro == "Trout Fishing in America") %>%
mutate(author = "Trout Fishing in America")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
# Plot - usa i nomi completi delle colonne
ggplot(freq_vento_vs_trota, aes(x = `Trout Fishing in America`, y = `Hear the Wind Sing`)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = "Hear the Wind Sing vs Trout Fishing in America",
x = "Trout Fishing in America — Brautigan",
y = "Hear the Wind Sing — Murakami") +
theme_bw()
## Warning: Removed 4766 rows containing missing values or values outside the scale range
## (`geom_text()`).
# Correlazione - usa i nomi completi
cor.test(freq_vento_vs_trota$`Trout Fishing in America`,
freq_vento_vs_trota$`Hear the Wind Sing`)
##
## Pearson's product-moment correlation
##
## data: freq_vento_vs_trota$`Trout Fishing in America` and freq_vento_vs_trota$`Hear the Wind Sing`
## t = 21.295, df = 1325, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.4637394 0.5439821
## sample estimates:
## cor
## 0.504951
# 4. IL GRANDE GATSBY vs TUTTI I LIBRI DI MURAKAMI
# Trova quale libro di Murakami è più simile al Grande Gatsby
risultati_gatsby <- tibble(libro = character(), correlazione = numeric())
libri_murakami <- unique(libri_tidy$libro)
for (l in libri_murakami) {
freq_temp <- bind_rows(
libri_tidy %>% filter(libro == l) %>% mutate(author = "Murakami"),
altri_tidy %>% filter(libro == "The Great Gatsby") %>% mutate(author = "Gatsby")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion) %>%
filter(!is.na(Murakami), !is.na(Gatsby))
cor_val <- cor(freq_temp$Gatsby, freq_temp$Murakami, use = "complete.obs")
risultati_gatsby <- risultati_gatsby %>%
add_row(libro = l, correlazione = cor_val)
}
# Trova libro più correlato
migliore_gatsby <- risultati_gatsby %>%
slice_max(correlazione, n = 1)
cat("\nMost correlated book to The Great Gatsby:", migliore_gatsby$libro,
"— Correlation:", round(migliore_gatsby$correlazione, 3), "\n")
##
## Most correlated book to The Great Gatsby: 8-The Wind-Up Bird Chronicle — Correlation: 0.67
# Plot del migliore
freq_plot_gatsby <- bind_rows(
libri_tidy %>% filter(libro == migliore_gatsby$libro) %>% mutate(author = "Murakami"),
altri_tidy %>% filter(libro == "The Great Gatsby") %>% mutate(author = "Gatsby")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
ggplot(freq_plot_gatsby, aes(x = Gatsby, y = Murakami)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = paste("The Great Gatsby vs", migliore_gatsby$libro),
x = "The Great Gatsby — Fitzgerald",
y = paste(migliore_gatsby$libro, "— Murakami")) +
theme_bw()
## Warning: Removed 9661 rows containing missing values or values outside the scale range
## (`geom_text()`).
# 5. IL LUNGO ADDIO vs TUTTI I LIBRI DI MURAKAMI
risultati_addio <- tibble(libro = character(), correlazione = numeric())
for (l in libri_murakami) {
freq_temp <- bind_rows(
libri_tidy %>% filter(libro == l) %>% mutate(author = "Murakami"),
altri_tidy %>% filter(libro == "The Long Goodbye") %>% mutate(author = "Addio")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion) %>%
filter(!is.na(Murakami), !is.na(Addio))
cor_val <- cor(freq_temp$Addio, freq_temp$Murakami, use = "complete.obs")
risultati_addio <- risultati_addio %>%
add_row(libro = l, correlazione = cor_val)
}
# Trova libro più correlato
migliore_addio <- risultati_addio %>%
slice_max(correlazione, n = 1)
cat("\nMost correlated book to The Long Goodbye:", migliore_addio$libro,
"— Correlation:", round(migliore_addio$correlazione, 3), "\n")
##
## Most correlated book to The Long Goodbye: 6-Dance Dance Dance — Correlation: 0.723
# Plot del migliore
freq_plot_addio <- bind_rows(
libri_tidy %>% filter(libro == migliore_addio$libro) %>% mutate(author = "Murakami"),
altri_tidy %>% filter(libro == "The Long Goodbye") %>% mutate(author = "Addio")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
ggplot(freq_plot_addio, aes(x = Addio, y = Murakami)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = paste("The Long Goodbye vs", migliore_addio$libro),
x = "The Long Goodbye — Chandler",
y = paste(migliore_addio$libro, "— Murakami")) +
theme_bw()
## Warning: Removed 8871 rows containing missing values or values outside the scale range
## (`geom_text()`).
# 6. PRIMO VS ULTIMO LIBRO DI MURAKAMI
# Identifica primo e ultimo per anno
cat("\nFirst book:", "1-Hear the Wind Sing")
##
## First book: 1-Hear the Wind Sing
cat("\nLast book:", "15-The City and Its Uncertain", "\n")
##
## Last book: 15-The City and Its Uncertain
# Frequenze primo vs ultimo
freq_primo_ultimo <- bind_rows(
libri_tidy %>% filter(libro == "1-Hear the Wind Sing") %>% mutate(author = "First"),
libri_tidy %>% filter(libro == "15-The City and Its Uncertain") %>% mutate(author = "Last")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion) %>%
drop_na()
# Plot
ggplot(freq_primo_ultimo, aes(x = First, y = Last)) +
geom_abline(color = "gray40", lty = 2) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5, size = 2.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
labs(title = "First vs Last book",
x = paste("First —", "1-Hear the Wind Sing"),
y = paste("Last —", "15-The City and Its Uncertain")) +
theme_bw()
# Correlazione
cor.test(freq_primo_ultimo$First, freq_primo_ultimo$Last)
##
## Pearson's product-moment correlation
##
## data: freq_primo_ultimo$First and freq_primo_ultimo$Last
## t = 27.442, df = 1941, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.4958980 0.5600109
## sample estimates:
## cor
## 0.5287081
# TF-IDF distintivo
tf_idf_confronto <- libri_tidy %>%
filter(libro %in% c("1-Hear the Wind Sing", "15-The City and Its Uncertain")) %>%
count(libro, word) %>%
bind_tf_idf(word, libro, n) %>%
arrange(desc(tf_idf)) %>%
group_by(libro) %>%
top_n(15, tf_idf) %>%
ungroup()
tf_idf_confronto %>%
mutate(word = reorder_within(word, tf_idf, libro)) %>%
ggplot(aes(word, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 2, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive words: first vs last book",
x = NULL,
y = "tf-idf")
# Trova parole presenti in ENTRAMBI i libri
parole_comuni_primo_ultimo <- freq_primo_ultimo %>%
filter(!is.na(First), !is.na(Last)) %>%
mutate(media_prop = (First + Last) / 2) %>%
slice_max(media_prop, n = 20) %>%
arrange(desc(media_prop))
parole_comuni_primo_ultimo
## # A tibble: 20 × 4
## word First Last media_prop
## <chr> <dbl> <dbl> <dbl>
## 1 time 0.0130 0.0121 0.0125
## 2 town 0.00288 0.0126 0.00772
## 3 people 0.00588 0.00454 0.00521
## 4 library 0.000240 0.00818 0.00421
## 5 rat 0.00840 0.0000206 0.00421
## 6 head 0.00360 0.00413 0.00387
## 7 day 0.00276 0.00465 0.00370
## 8 yeah 0.00720 0.000123 0.00366
## 9 looked 0.00384 0.00341 0.00363
## 10 boy 0.000120 0.00701 0.00356
## 11 beer 0.00684 0.0000822 0.00346
## 12 night 0.00360 0.00282 0.00321
## 13 girl 0.00504 0.00127 0.00316
## 14 eyes 0.00216 0.00413 0.00315
## 15 left 0.00264 0.00329 0.00296
## 16 nodded 0.00396 0.00173 0.00284
## 17 wall 0.000360 0.00520 0.00278
## 18 hand 0.00396 0.00150 0.00273
## 19 world 0.000480 0.00475 0.00261
## 20 words 0.00216 0.00296 0.00256
# Visualizzazione parole comuni
parole_comuni_primo_ultimo %>%
mutate(word = reorder(word, media_prop)) %>%
ggplot(aes(word, media_prop)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = paste("Most common words —", "1-Hear the Wind Sing", "and", "15-The City and Its Uncertain"),
x = NULL,
y = "Average relative frequency") +
theme_bw()
# 7. TEMI RICORRENTI (PAROLE PRESENTI IN MOLTI LIBRI)
# Parole presenti in almeno 10 libri
temi_ricorrenti <- libri_tidy %>%
distinct(libro, word) %>%
count(word) %>%
filter(n >= 10) %>%
arrange(desc(n))
temi_ricorrenti
## # A tibble: 3,493 × 2
## word n
## <chr> <int>
## 1 1 15
## 2 abandoned 15
## 3 address 15
## 4 afraid 15
## 5 afternoon 15
## 6 age 15
## 7 aged 15
## 8 ago 15
## 9 ahead 15
## 10 air 15
## # ℹ 3,483 more rows
# Top temi per libro
libri_tidy %>%
filter(libro %in% levels(libro)[1:8]) %>%
semi_join(temi_ricorrenti, by = "word") %>%
count(libro, word) %>%
group_by(libro) %>%
mutate(proportion = n / sum(n)) %>%
slice_max(proportion, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, proportion, libro)) %>%
ggplot(aes(word, proportion, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Recurring themes per book (words in 10+ books)",
x = NULL,
y = "Relative frequency")
libri_tidy %>%
filter(libro %in% levels(libro)[9:15]) %>%
semi_join(temi_ricorrenti, by = "word") %>%
count(libro, word) %>%
group_by(libro) %>%
mutate(proportion = n / sum(n)) %>%
slice_max(proportion, n = 10) %>%
ungroup() %>%
mutate(word = reorder_within(word, proportion, libro)) %>%
ggplot(aes(word, proportion, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 3, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Recurring themes per book (words in 10+ books)",
x = NULL,
y = "Relative frequency")
# 8. EVOLUZIONE TEMPORALE
libri_ordinati <- dfM %>%
distinct(libro, anno) %>%
arrange(anno)
libri_seq <- as.character(libri_ordinati$libro)
correlazioni_seq <- tibble(
libro1 = character(),
libro2 = character(),
correlazione = numeric()
)
for (i in 1:(length(libri_seq) - 1)) {
freq_temp <- bind_rows(
libri_tidy %>% filter(libro == libri_seq[i]) %>% mutate(author = "Book1"),
libri_tidy %>% filter(libro == libri_seq[i+1]) %>% mutate(author = "Book2")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion) %>%
filter(!is.na(Book1), !is.na(Book2))
cor_val <- cor(freq_temp$Book1, freq_temp$Book2, use = "complete.obs")
cat(i, "|", libri_seq[i], "→", libri_seq[i+1],
"| cor:", round(cor_val, 3), "\n")
correlazioni_seq <- correlazioni_seq %>%
add_row(libro1 = libri_seq[i],
libro2 = libri_seq[i+1],
correlazione = cor_val)
}
## 1 | 1-Hear the Wind Sing → 2-Pinball, 1973 | cor: 0.714
## 2 | 2-Pinball, 1973 → 3-A Wild Sheep Chase | cor: 0.54
## 3 | 3-A Wild Sheep Chase → 4-Hard Boiled Wonderland and the End of the World | cor: 0.533
## 4 | 4-Hard Boiled Wonderland and the End of the World → 5-Norwegian wood | cor: 0.639
## 5 | 5-Norwegian wood → 6-Dance Dance Dance | cor: 0.709
## 6 | 6-Dance Dance Dance → 7-South of the Border West of the Sun | cor: 0.742
## 7 | 7-South of the Border West of the Sun → 8-The Wind-Up Bird Chronicle | cor: 0.751
## 8 | 8-The Wind-Up Bird Chronicle → 9-Sputnik Sweetheart | cor: 0.548
## 9 | 9-Sputnik Sweetheart → 10-Kafka On The Shore | cor: 0.813
## 10 | 10-Kafka On The Shore → 11-After Dark | cor: 0.71
## 11 | 11-After Dark → 12-1Q84 | cor: 0.699
## 12 | 12-1Q84 → 13-Colorless Tsukuru Tazaki | cor: 0.851
## 13 | 13-Colorless Tsukuru Tazaki → 14-Killing Commendatore | cor: 0.794
## 14 | 14-Killing Commendatore → 15-The City and Its Uncertain | cor: 0.623
# Plot
correlazioni_seq %>%
mutate(ordine = row_number(),
coppia = paste(str_trunc(libro1, 20), "→", str_trunc(libro2, 20))) %>%
ggplot(aes(x = reorder(coppia, ordine), y = correlazione)) +
geom_col(fill = "steelblue") +
geom_hline(yintercept = mean(correlazioni_seq$correlazione),
lty = 2, color = "red") +
coord_flip() +
labs(title = "Correlation between consecutive books (by year)",
x = NULL,
y = "Correlation") +
theme_minimal()
# 9. LIBRI POPOLARI
libri_popolari <- c("5-Norwegian wood",
"12-1Q84",
"10-Kafka On The Shore")
# TF-IDF libri popolari
libri_tidy %>%
filter(libro %in% libri_popolari) %>%
count(libro, word) %>%
bind_tf_idf(word, libro, n) %>%
group_by(libro) %>%
top_n(15, tf_idf) %>%
ungroup() %>%
mutate(word = reorder_within(word, tf_idf, libro)) %>%
ggplot(aes(word, tf_idf, fill = libro)) +
geom_col(show.legend = FALSE) +
facet_wrap(~libro, ncol = 2, scales = "free") +
coord_flip() +
scale_x_reordered() +
labs(title = "Distinctive words — Popular books",
x = NULL,
y = "tf-idf")
# Parole comuni tra libri popolari
libri_tidy %>%
filter(libro %in% libri_popolari) %>%
count(libro, word) %>%
group_by(word) %>%
filter(n() == length(libri_popolari)) %>%
ungroup() %>%
group_by(libro) %>%
mutate(proportion = n / sum(n)) %>%
ungroup() %>%
group_by(word) %>%
summarise(media_prop = mean(proportion)) %>%
slice_max(media_prop, n = 20) %>%
mutate(word = reorder(word, media_prop)) %>%
ggplot(aes(word, media_prop)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Common words among popular books",
x = NULL,
y = "Average relative frequency") +
theme_bw()
# Correlazioni tra libri popolari
for (i in 1:(length(libri_popolari) - 1)) {
for (j in (i + 1):length(libri_popolari)) {
freq_temp <- bind_rows(
libri_tidy %>% filter(libro == libri_popolari[i]) %>% mutate(author = "Book1"),
libri_tidy %>% filter(libro == libri_popolari[j]) %>% mutate(author = "Book2")
) %>%
mutate(word = str_extract(word, "[a-z']+")) %>%
count(author, word) %>%
group_by(author) %>%
mutate(proportion = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = author, values_from = proportion)
cor_val <- cor(freq_temp$Book1, freq_temp$Book2, use = "complete.obs")
cat("\nCorrelation", libri_popolari[i], "-", libri_popolari[j],
":", round(cor_val, 3))
}
}
##
## Correlation 5-Norwegian wood - 12-1Q84 : 0.74
## Correlation 5-Norwegian wood - 10-Kafka On The Shore : 0.715
## Correlation 12-1Q84 - 10-Kafka On The Shore : 0.723
In questa sezione andiamo ad analizzare le correlazioni che sussistono tra i vari libri (i 15 libri di Murakami e i 3 libri di autori di cui Murakami è stato fortemente influenzato)
L’analisi si basa sul coefficiente di correlazione di Pearson applicato alle frequenze relative delle parole. Questo metodo consente di misurare quanto due testi condividano lo stesso vocabolario concentrandosi così sulla struttura linguistica dei testi.
Gli autori e i libri che ho selezionato per il confronto sono:
La correlazione più alta emerge con The Long Goodbye di Raymond Chandler (0.705), Questo dato conferma che Murakami è uno scrittore hard-boiled (autore di narrativa poliziesca che descrive il crimine in modo crudo e realistico, distaccandosi dall’enigma deduttivo classico). L’influenza chandleriana si manifesta attraverso un narratore in prima persona solitario e disincantato, ambientazioni urbane notturne, dialoghi asciutti e una violenza descritta senza moralismo.
Le parole condivise più frequenti (“world”, “eyes”, “looked”, “hand”, “time”) rimandano a una percezione fenomenologica della realtà, mentre termini come “bar”, “drink”, “guy”, “gun”, “cop” collocano l’esperienza narrativa in uno spazio noir riconoscibile. Murakami utilizza lo stile noir hard-boiled per raccontare la disillusione e l’alienazione delle persone attraverso il surrealismo e la malinconia.
La seconda affinità significativa emerge con George Orwell (1984, 0.586) anche se i valori non sono particolarmente alti. Di base lo stile di Murakami è meno linguistico e più tematico. Murakami condivide con Orwell l’idea di un potere pervasivo e impersonale (Big Brother trova un’eco nei Little People di 1Q84) e una profonda sfiducia nella possibilità di accedere a una realtà oggettiva e stabile. Tuttavia, mentre Orwell utilizza un vocabolario politico esplicito (“party”, “ministry”, “comrade”), Murakami preferisce un lessico psicologico ed emotivo (“feel”, “moment”, “love”). Orwell infatti descrive il funzionamento del potere mentre Murakami descrive l’effetto del potere sull’individuo.
Con F. Scott Fitzgerald (The Great Gatsby, 0.540) la somiglianza è moderata e passa soprattutto attraverso temi condivisi come la nostalgia per il passato, l’amore irraggiungibile e l’illusione di una felicità sempre rimandata. I due autori infatti sono particolarmente diversi sul piano stilistico. Fitzgerald costruisce un mondo barocco e glamour, dominato da nomi propri e simboli di ricchezza mentre Murakami riduce il linguaggio all’ordinario, privilegiando parole neutre e universali come “time”, “people” ed “eyes”.
Nel caso di Richard Brautigan, la correlazione è decisamente bassa (0.371), nonostante Murakami lo abbia tradotto e citato spesso come influenza. Probabilmente Murakami ha riutilizzato l’attitudine di Brautigan (ironia, frammentazione, leggerezza apparente) ma non il suo lessico. La bassa correlazione non indica quindi assenza di influenza, ma una influenza di tipo concettuale e meno linguistico.
Il confronto tra 1Q84 e 1984 (correlazione 0.575) conferma la natura di 1Q84 come omaggio tematico piuttosto che riscrittura. Il titolo del libro segnala esplicitamente la correlazione con Orwell (in giapponese, “Q” si pronuncia “kyū” = “9”), ma il suo vocabolario politico viene sostituito da uno emotivo e relazionale. Non è il sistema a essere descritto, ma la solitudine di chi lo abita.
Un discorso simile vale per Hear the Wind Sing e Trout Fishing in America (0.505). Entrambi sono esordi frammentari, costruiti su capitoli brevissimi e una voce narrante disillusa. Tuttavia, Murakami urbanizza l’estetica sostituendo la natura e l’ambiente con bar, città e riferimenti letterari. La tecnica è appresa, ma il contesto è già autonomo.
Successivamente sono andata a individuare il libro che più si avvicinasse al The Great Gatsby ed è risultato The Wind-Up Bird Chronicle (0.67). Il libro rivela una sorprendente affinità strutturale dal momento che in entrambi i casi abbiamo un narratore passivo che osserva il collasso di un sogno legato a una donna perduta, mentre il passato ritorna sotto forma di trauma. Tuttavia, Murakami sostituisce i nomi propri con simboli naturali, spostando il centro emotivo dall’individuo al mito.
Secondo l’analisi, la correlazione più alta è quella di The Long Goodbye e Dance Dance Dance (0.723). Dance Dance Dance è il romanzo in cui Murakami si avvicina di più al modello hard-boiled con un protagonista senza nome, investigazione informale, hotel come spazio liminale, violenza e corruzione. Qui Murakami non si limita a essere influenzato da Chandler, lo riscrive.
Il confronto tra il primo e l’ultimo romanzo (Hear the Wind Sing, 1979, e The City and Its Uncertain Walls, 2023) restituisce una correlazione moderata (0.529) che, considerato i 44 anni di distanza tra i due libri, può essere considerato un valore abbastanza alto. Le parole chiave condivise — “time”, “town”, “people” — indicano che il nucleo tematico ed esistenziale di Murakami è rimasto di per se invariato, mentre il lessico si sviluppa da uno colloquiale e americanizzato a uno simbolico e mitologico.
L’analisi delle correlazioni tra libri consecutivi mostra invece che Murakami nel tempo diventa sempre più simile a se stesso. Le correlazioni aumentano man mano che i libri vengono pubblicati e raggiungendo valori alti nelle opere più recenti. Questo suggerisce una progressiva cristallizzazione dello stile dello scrittore che si focalizza sempre meno sullo sperimentare e sempre di più sulla perfezione formale.
Con la correlazione possiamo andare a individuare 4 periodi: “Period 1 (1979-1985)” = “1-Hear the Wind Sing”, “2-Pinball, 1973”, “3-A Wild Sheep Chase”, che rappresenta la prima trillogia di murakami
“Period 2 (1985-1995)” = c(“4-Hard Boiled Wonderland and the End of the World”, “5-Norwegian wood”,“6-Dance Dance Dance”,“7-South of the Border West of the Sun”),
“Period 3 (1995-2005)” = c(“8-The Wind-Up Bird Chronicle”, “9-Sputnik Sweetheart”,“10-Kafka On The Shore”),
“Period 4 (2005-2023)” = c(“11-After Dark”, “12-1Q84”,“13-Colorless Tsukuru Tazaki”,“14-Killing Commendatore”,“15-The City and Its Uncertain”)
Se vogliamo cercare di individuare un patter strutturale tra i libri notiamo che circa 3.500 parole compaiono in almeno 10 dei 15 romanzi analizzati e costistuiscono un vero e proprio vocabolario murakamiano standard. Parole legate al tempo, alla percezione, al corpo e all’esistenza dominano costantemente, indipendentemente dalla trama ( “time”, “people”, “eyes”, “looked” “head”, “hand”, “day”, “night” “left”, “world”). Questo conferma l’esistenza di una palette lessicale stabile, su cui Murakami costruisce variazioni minime. La sua evoluzione non è stata rivoluzionaria ma incrementale: ogni libro aggiunge sfumature a una formula che si perfeziona senza mai rompersi.
Per concludere, sono andata ad analizzare i 3 romanzi più famosi dell’autore cercando di individuare i pattern ricorrenti che più vengono amati dal pubblico murakamiano. I libri sono Norwegian Wood, Kafka on the Shore e 1Q84. L’analisi delle correlazioni lessicali tra questi testi mostra valori elevati: Norwegian Wood–1Q84 (0.740), Norwegian Wood–Kafka on the Shore (0.715) e Kafka on the Shore–1Q84 (0.723), con una media complessiva di 0.726. Si tratta di una correlazione significativamente superiore alla media dell’intero corpus, indicando che i romanzi più amati dal pubblico sono anche quelli più simili tra loro dal punto di vista linguistico.
Dal punto di vista lessicale, questi testi condividono un nucleo comune estremamente stabile: parole come time, people, eyes, world, life, hand, head e looked compaiono con alta frequenza in tutti e tre. Questo vocabolario non rimanda a eventi specifici o a trame particolari, ma a una dimensione esistenziale astratta, fatta di percezione, corporeità minima e riflessione sul tempo. È proprio questa neutralità semantica, oscillante tra introspezione e malinconia, a costituire il cuore riconoscibile dell’esperienza murakamiana.
Le differenze tra i tre romanzi emergono invece nella distribuzione dei nomi propri. Norwegian Wood è dominato da nomi femminili (Naoko, Midori, Reiko), confermando la sua natura fortemente sentimentale e relazionale. Kafka on the Shore presenta un cast più ampio e corale, riflettendo una struttura narrativa complessa e frammentata. 1Q84, infine, mostra una perfetta parità tra protagonisti maschili e femminili (Tengo e Aomame), coerente con la sua costruzione duale. Tuttavia, al di sotto di queste differenze superficiali, il vocabolario di base rimane sorprendentemente uniforme.
Questi dati suggeriscono l’esistenza di una vera e propria formula linguistica del successo murakamiano. I romanzi che hanno avuto maggiore risonanza sono quelli che utilizzano in modo più puro e riconoscibile il lessico esistenziale standard dell’autore, senza deviazioni stilistiche marcate.
Questo risultato rafforza una delle conclusioni centrali dell’analisi comparativa nel quale si evidenzia come Murakami è diventato progressivamente un autore che scrive “per somiglianza”. La coerenza lessicale costituisce la chiave della sua riconoscibilità globale.
Il risultato è un corpus di 15 romanzi che sono variazioni sul tema dell’individuo smarrito nel tempo, che cerca connessioni fragili in un mondo opaco e alienante.